-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
39 changed files
with
1,181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# Vulcan | ||
|
||
The Vulcan project helps developers install and maintain CLI tooling and packages on their Mac OSX machines across a | ||
variety of package managers. Vulcan uses a declarative YAML based configuration file to pin the packages and versions | ||
that should be installed on the machine. | ||
|
||
The main interface is through a CLI tool called `vulcan`, which will automatically look for a file named | ||
`vulcan-config.yml` in your current directory unless passed the `--config` option. | ||
|
||
## Pre-requisites | ||
|
||
At minimum the Mac OSX environment should have [homebrew](https://brew.sh) and [yq](https://github.com/mikefarah/yq) | ||
installed. | ||
|
||
|
||
## Usage | ||
|
||
```bash | ||
Usage: vulcan ACTION [OPTIONS] | ||
|
||
ACTIONS: | ||
install Installs development tools | ||
help Prints this usage menu | ||
|
||
|
||
Global OPTIONS: | ||
--config Path to the configuration file (default: vulcan-config.yml) | ||
--log-level The log level (default INFO) | ||
--dry-run Flag to indicate that the install is just a dry-run | ||
``` | ||
|
||
|
||
### vulcan install | ||
|
||
``` | ||
vulcan install [--config <vulcan-config.yml>] | ||
``` | ||
|
||
Vulcan can help developers install and maintain other CLI tools and packages necessary for development. It utilizes a | ||
declarative specification to define the packages that should be installed, the desired versions of those packages, | ||
and the package manager (a.k.a installer) that should be used to install those packages. | ||
|
||
Sample configuration in `vulcan-config.yml`: | ||
|
||
```yaml | ||
installers: | ||
- name: brew | ||
- name: asdf | ||
|
||
programs: | ||
- name: awscli | ||
installer: brew | ||
- name: asdf | ||
installer: brew | ||
- name: direnv | ||
installer: asdf | ||
- name: helm | ||
installer: asdf | ||
version: 3.3.4 | ||
- name: nodejs | ||
installer: asdf | ||
version: 14.13.0 | ||
``` | ||
*NOTE:* If a package version is omitted, then Vulcan assumes that you want to track the `latest` version of that package, | ||
and will check for updates every time the `install` command is run. | ||
|
||
|
||
Currently Vulcan supports installations through the following package managers: | ||
* brew (homebrew) | ||
* asdf | ||
* sdk (sdkman) | ||
* pipx | ||
* nvm (nodejs versions) | ||
* pyenv (python versions) | ||
* tfenv (terraform versions) | ||
|
||
*NOTE:* We highly recommend that were possible you avoid using `brew` as the package manager as it does not allow you | ||
to pin the exact minor version of a package that you need. Homebrew also generally promotes an upgrade only model and | ||
makes it rather difficult to downgrade to a specific minor version of a package. | ||
|
||
*NOTE:* We also highly recommend to use the `asdf` package manager when possible, as this package manager supports using | ||
a configuration file call `.tool-versions` within specific directories to pin which versions of a package should be | ||
activated when navigating into that directory (similar to direnv). This `.tool-versions` file can be committed to | ||
version control to synchronize environment and package requirements amongst a team of developers. | ||
|
||
|
||
## Future Enhancements | ||
|
||
* Docker container rather than local dependencies. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env zsh | ||
|
||
# Stop script execution on any errors | ||
set -euE -o pipefail | ||
|
||
USAGE=" | ||
Usage: $(basename $0) ACTION [OPTIONS] | ||
ACTIONS: | ||
install Installs development tools | ||
help Prints this usage menu | ||
Global Options: | ||
--config Path to the configuration file (default: vulcan-config.yml) | ||
--log-level The log level (default INFO) | ||
--dry-run Flag to indicate that the install is just a dry-run | ||
" | ||
|
||
# Imports | ||
DIR="$( cd "$( dirname "${(%):-%x}" )" >/dev/null 2>&1 && pwd )" | ||
source ${DIR}/lib/install.sh | ||
|
||
# Arguments | ||
ACTION=$(parse_arg 'ACTION' 1 'install' "$@") | ||
DRY_RUN=$(parse_long_opt 'dry-run' 'true' "$@") | ||
CONFIG=$(parse_long_opt 'config' '' "$@") | ||
CONFIG=${CONFIG:-"vulcan-config.yml"} | ||
LOGLEVEL=$(parse_long_opt 'log-level' '' "$@") | ||
LOGLEVEL=${LOGLEVEL:-"INFO"} | ||
|
||
# Run the desired command | ||
|
||
case "${ACTION}" in | ||
install) | ||
install_from_config "${CONFIG}" | ||
;; | ||
esac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
installers: | ||
# - name: brew | ||
# - name: asdf | ||
# - name: sdk | ||
- name: pipx | ||
# - name: nvm | ||
# - name: pyenv | ||
# - name: tfenv | ||
|
||
packages: | ||
# - name: zsh | ||
# installer: brew | ||
# - name: git | ||
# installer: brew | ||
# - name: dos2unix | ||
# installer: brew | ||
# - name: jq | ||
# installer: brew | ||
# - name: yq | ||
# installer: brew | ||
# - name: unzip | ||
# installer: brew | ||
# - name: coreutils | ||
# installer: brew | ||
# - name: gnupg | ||
# installer: brew | ||
# - name: bison | ||
# installer: brew | ||
# - name: gettext | ||
# installer: brew | ||
# - name: gnu-tar | ||
# installer: brew | ||
# - name: zlib | ||
# installer: brew | ||
# - name: curl | ||
# installer: brew | ||
# - name: awscli | ||
# installer: brew | ||
# - name: serverless | ||
# installer: brew | ||
# - name: direnv | ||
# installer: asdf | ||
# - name: helm | ||
# installer: asdf | ||
# version: 3.3.4 | ||
# plugins: | ||
# - https://github.com/hypnoglow/helm-s3.git | ||
# - https://github.com/databus23/helm-diff | ||
# - name: helmsman | ||
# installer: asdf | ||
# version: 3.4.4 | ||
# - name: kubectl | ||
# installer: asdf | ||
# version: 1.19.2 | ||
# - name: nodejs | ||
# installer: asdf | ||
# version: 14.13.0 | ||
# - name: packer | ||
# installer: asdf | ||
# version: 1.6.4 | ||
# - name: python | ||
# installer: asdf | ||
# version: 3.8.5 | ||
# - name: poetry | ||
# installer: asdf | ||
# - name: terraform | ||
# installer: asdf | ||
# version: 0.12.29 | ||
# - name: java | ||
# installer: sdk | ||
# version: 11.0.8-amzn | ||
# - name: groovy | ||
# installer: sdk | ||
# version: 3.0.6 | ||
- name: aws-sso-credential-process | ||
installer: pipx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
require() { | ||
: <<DOC | ||
Accepts an array of variable names and checks if each of those variables is set | ||
and not empty | ||
-------------------------------------------------------------------------------- | ||
VARS A series of variable names passed as function arguments | ||
DOC | ||
VARS=("$@") | ||
for VAR in "${VARS[@]}"; do | ||
if [[ -z ${!VAR} ]]; then | ||
log_error "Option '--$(echo ${VAR} | tr '[:upper:]' '[:lower:]')' is required" | ||
return 1 | ||
fi | ||
done | ||
return 0 | ||
} | ||
|
||
parse_arg() { | ||
: <<DOC | ||
Parses a positional argument supplied to the command and returns it's value | ||
-------------------------------------------------------------------------------- | ||
ARG The name to give the positional argument | ||
IDX The index of the positional argument | ||
VALUES (Optional) The acceptable range of values for this argument | ||
DOC | ||
local ARG=$1 && shift | ||
local IDX=$1 && shift | ||
local VALUES=$1 && shift | ||
local VAL=${argv[IDX]} | ||
# check if no value was supplied | ||
if [[ ${VAL} =~ ^\- ]] || [[ -z ${VAL} ]]; then | ||
log_error "Argument '${ARG}' requires a value to be supplied" | ||
return 1 | ||
fi | ||
# check if value is in list of acceptable values | ||
if [[ ! -z "${VALUES}" ]]; then | ||
declare a=("${(@s/|/)VALUES}") | ||
if [[ ! ${a[(ie)${VAL}]} -le ${#a} ]]; then | ||
log_error "Argument '${ARG}=${VAL}' is invalid. Valid values are [${VALUES}]" | ||
return 1 | ||
fi | ||
fi | ||
echo ${VAL} | ||
return 0 | ||
} | ||
|
||
parse_long_opt() { | ||
: <<DOC | ||
Parses a long option supplied to the command and returns it's value | ||
-------------------------------------------------------------------------------- | ||
OPT The name of the option to parse | ||
VALUES (Optional) The acceptable range of values for this option | ||
DOC | ||
local OPT=$1 && shift | ||
local VALUES=$1 && shift | ||
local ARGS=("$@") | ||
local OPT_IDX= | ||
local VAL_IDX= | ||
local VAL= | ||
# identify the position of the option | ||
regexp="--${OPT}" | ||
for i in {1..$#argv}; do | ||
[[ ${argv[i]} =~ $regexp ]] && OPT_IDX=${i} && break | ||
done | ||
# if not found, then set val to null | ||
if [[ -z "${OPT_IDX}" ]]; then | ||
VAL= | ||
# else if option is a boolean, then value is just "true" | ||
elif [[ "${VALUES}" == "true" ]]; then | ||
VAL="true" | ||
# else if format [opt=val], then attempt to get value from splitting on the '=' | ||
elif [[ ${argv[OPT_IDX]} =~ ^(.*)=(.*)$ ]]; then | ||
VAL=${argv[OPT_IDX]#*=} | ||
# check if no value was supplied | ||
if [[ -z ${VAL} ]]; then | ||
log_error "Option '--${OPT}' requires a value to be supplied" | ||
return 1 | ||
fi | ||
# else (format [opt val]), attempt then grab value from next positional argument | ||
else | ||
VAL_IDX=$((OPT_IDX+1)) | ||
VAL=${argv[VAL_IDX]} | ||
# check if no value was supplied | ||
if [[ ${VAL} =~ ^\- ]] || [[ -z ${VAL} ]]; then | ||
log_error "Option '--${OPT}' requires a value to be supplied" | ||
return 1 | ||
fi | ||
fi | ||
# check value against range of acceptable values | ||
if [[ ! -z ${VAL} ]] && [[ ! -z "${VALUES}" ]]; then | ||
declare a=("${(@s/|/)VALUES}") | ||
if [[ ! ${a[(ie)${VAL}]} -le ${#a} ]]; then | ||
log_error "Option --${OPT}=${VAL} is invalid. Valid options are [${VALUES}]" | ||
return 1 | ||
fi | ||
fi | ||
echo ${VAL} | ||
return 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
INCLUDE_DIR="$( cd "$( dirname "${(%):-%x}" )" >/dev/null 2>&1 && pwd )" | ||
source ${INCLUDE_DIR}/args.sh | ||
source ${INCLUDE_DIR}/logs.sh | ||
|
||
rr() { | ||
find $1 -name "$2" -type f | ||
} | ||
|
||
grep2() { | ||
grep -lZR "$1" | xargs -0 grep -l "$2" | ||
} | ||
|
||
quote() { | ||
ruby -rcsv -ne 'puts CSV.generate_line(CSV.parse_line($_), :force_quotes=>true)' $1 | ||
} | ||
|
||
version() { | ||
: <<DOC | ||
Parses a semantic version so that it can easily be compared to another semantic | ||
version using less than and greater than operators. | ||
-------------------------------------------------------------------------------- | ||
DOC | ||
echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }' | ||
return 0 | ||
} | ||
|
||
func_exists() { | ||
# appended double quote is an ugly trick to make sure we do get a string -- if $1 is not a known command, type does not output anything | ||
[ x$(type -t $1) = xfunction ]; | ||
} | ||
|
||
require_tool() { | ||
local TOOL=$1 && shift | ||
if ! command -v ${TOOL} &> /dev/null; then | ||
log_error "The '${TOOL}' command is required but cannot be found!" && exit 1 | ||
fi | ||
} | ||
|
||
get_github_latest_release() { | ||
local REPO=$1 && shift | ||
curl --silent "https://api.github.com/repos/$REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env zsh | ||
|
||
_DIR="$( cd "$( dirname "${(%):-%x}" )" >/dev/null 2>&1 && pwd )" | ||
source ${_DIR}/common.sh | ||
source ${_DIR}/installers/darwin.sh | ||
source ${_DIR}/installers/asdf.sh | ||
source ${_DIR}/installers/brew.sh | ||
|
||
install_from_config() { | ||
local CONFIG=$1 | ||
local LENGTH | ||
local PROGRAM | ||
local INSTALLER | ||
local VERSION | ||
|
||
LENGTH=$(yq r "${CONFIG}" --length installers) | ||
for ((i=0; i<=LENGTH-1; i++)); do | ||
INSTALLER=$(yq r "${CONFIG}" "installers[$i].name") | ||
|
||
log_info "Found installer ${YELLOW}${INSTALLER}${NC} in configuration file" | ||
|
||
darwin_install_or_upgrade_installer "${INSTALLER}" | ||
echo "\n" | ||
done | ||
|
||
|
||
LENGTH=$(yq r "${CONFIG}" --length packages) | ||
for ((i=0; i<=LENGTH-1; i++)); do | ||
PROGRAM=$(yq r "${CONFIG}" "packages[$i].name") | ||
INSTALLER=$(yq r "${CONFIG}" "packages[$i].installer") | ||
VERSION=$(yq r "${CONFIG}" "packages[$i].version") | ||
|
||
log_info "Found package ${YELLOW}${INSTALLER}:${PROGRAM}@${VERSION}${NC} in configuration file" | ||
|
||
darwin_install_or_upgrade_package "${INSTALLER}" "${PROGRAM}" "${VERSION}" | ||
echo "\n" | ||
done | ||
} |
Oops, something went wrong.