blob: f103fcde7f3a2ae928c2bf4b0b61346e90eebc7e [file] [log] [blame]
#!/bin/bash
# Usage:
# ./formula-fetch.sh <Formula URL> <Name> <Branch>
#
# Example usage:
# FORMULA_SOURCES=https://github.com/epcim/my-salt-formulas https://github.com/salt-formulas https://github.com/saltstack-formulas
# SALT_ENV_PATH=.vendor/formulas
# --
# ./formula-fetch.sh
# xargs -n1 ./formula-fetch.sh < dependencies.txt
## DEFAULTS
#
# default sources
FORMULA_SOURCES="${SALT_FORMULA_SOURCES:-https://github.com/salt-formulas https://github.com/saltstack-formulas}"
FORMULA_VERSION="${SALT_FORMULA_VERSION:-master}"
# where to fetch formulas
FORMULAS_BASE=${SALT_FORMULAS_BASE:-/srv/salt/formula}
# For better stability, skip formula repos without recognized CI
FORMULA_WITHOUT_CI=${SALT_FORMULA_WITHOUT_CI:-false}
# salt env/root, where formulas are found
SALT_ENV_PATH=${SALT_ENV_PATH:-/srv/salt/env/prd}
#SALT_ENV_PATH=${SALT_ENV_PATH:-.vendor/formulas}
#SALT_ENV_PATH=${SALT_ENV_PATH:/usr/share/salt-formulas/env/_formulas}
# reclass related
RECLASS_BASE=${RECLASS_BASE:-/srv/salt/reclass}
# env
LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
# Parse git dependencies from metadata.yml
# $1 - path to <formula>/metadata.yml
# sample to output:
# https://github.com/salt-formulas/salt-formula-git git
# https://github.com/salt-formulas/salt-formula-salt salt
function fetchDependencies() {
METADATA="$1";
grep -E "^dependencies:" "$METADATA" &>/dev/null || return 0
(python3 - "$METADATA" | while read dep; do fetchGitFormula $dep; done) <<-DEPS
import sys,yaml
try:
for dep in yaml.load(open(sys.argv[1], "r"))["dependencies"]:
if len(set(('name', 'source')) & set(dep.keys())) == 2:
print("{source} {name}".format(**dep))
except Exception as e:
print("[W] {}".format(e.__doc__))
print("[W] {}".format(e.message))
pass
DEPS
}
# Read formula name from meetadata.yml
# $1 - path to <formula>/metadata.yml
function getFormulaName() {
python3 - "$1" <<-READ_NAME
try:
import sys,yaml;print(yaml.load(open(sys.argv[1], "r"))["name"]);
except Exception as e:
print("[W] {}".format(e.__doc__))
print("[W] {}".format(e.message))
pass
READ_NAME
}
# Fetch formula from git repo
# $1 - formula git repo url
# $2 - formula name (optional)
# $3 - branch (optional)
function fetchGitFormula() {
test -n "${FETCHED}" || declare -a FETCHED=()
mkdir -p "$SALT_ENV_PATH" "$FORMULAS_BASE"
if [ -n "$1" ]; then
# set origin uri
# FIXME, TEMP fix for not yet up to date gh:salt-formulas -> s/tcpcloud/salt-formulas/
origin="${1/tcpcloud/salt-formulas}"
# set gh repo https://salt-formulas/salt-formula-salt -> $FORMULAS_BASE/salt-formulas/salt-formula-salt
repo=$(echo $origin | awk -F'/' '{ print substr($0, index($0,$4)) }')
# set normula name
test -n "$2" && name=$2 || name="$(echo ${origin//*\/} | sed -e 's/-formula$//' -e 's/^salt-formula-//' -e 's/^formula-//')"
# set branch
test -n "$3" && branch=$3 || branch=${FORMULA_VERSION}
# DEBUG
#echo '--- ------------------------------'
#echo origin, $origin
#echo repo, $repo
#echo fetched ${FETCHED[@]}
#echo -e name, $name
#echo '---'
#return
if ! [[ "${FETCHED[*]}" =~ $name ]]; then # dependency not yet fetched
echo -e "[I] Fetching: $origin -> $FORMULAS_BASE/$repo"
if [ -e "$FORMULAS_BASE/$repo" ]; then
pushd "$FORMULAS_BASE/$repo" &>/dev/null
git pull -r; git checkout $branch;
popd &>/dev/null
else
echo -e "[I] git clone $origin $FORMULAS_BASE/$repo -b $branch"
if ! git ls-remote --exit-code --heads $origin $branch; then
# Fallback to the master branch if the branch doesn't exist for this repository
branch=master
fi
if ! git clone "$origin" "$FORMULAS_BASE/$repo" -b "$branch"; then
echo -e "[E] Fetching formula from $origin failed."
return ${FAIL_ON_ERRORS:-0}
fi
fi
# A metadata.yml is github.com/salt-formulas specific
if [ ! -n "$2" -a -e "$FORMULAS_BASE/$repo/metadata.yml" ]; then
# try to update name as in formula metadata
name=$(getFormulaName "$FORMULAS_BASE/$repo/metadata.yml")
fi
# FIXME, better formula recognition/name fixup for saltstack formulas
# Recognize the repo is formula
if [ ! -e $FORMULAS_BASE/$repo/$name ]; then
echo -e "[E] The repository $FORMULAS_BASE/$repo was not recognized as formula repository."
rm -rf "$FORMULAS_BASE/$repo"
return ${FAIL_ON_ERRORS:-0}
fi
# Avoid checkout formulas/repos without CI
if ! $FORMULA_WITHOUT_CI; then
CI=false
for p in .circleci .travis.yml .kitchen.yml invoke.yml tasks.py tox.ini test tests; do
if [ -e "$FORMULAS_BASE/$repo/$p" ]; then
CI=true; break;
fi
done
if ! $CI; then
mv "$FORMULAS_BASE/$repo" "$FORMULAS_BASE/${repo}.deprecated-no-ci";
return ${FAIL_ON_ERRORS:-0}
fi
fi
# SET FORMULA IN SALT ENV
if [ ! -e "$SALT_ENV_PATH/$name" ]; then
# link formula
ln -svf $FORMULAS_BASE/$repo/$name $SALT_ENV_PATH/$name
# copy custom _states, _modules, _etc ...
for c in $(/bin/ls $FORMULAS_BASE/$repo | grep '^_' | xargs -n1 --no-run-if-empty); do
test -e $SALT_ENV_PATH/$c || mkdir -p $SALT_ENV_PATH/$c
ln -svf $FORMULAS_BASE/$repo/$c/* $SALT_ENV_PATH/$c
done
# install optional dependencies (python/pip related as of now only)
if [ -e $FORMULAS_BASE/$repo/requirements.txt ]; then
pip install -r $FORMULAS_BASE/$repo/requirements.txt
fi
# NOTE: github.com/salt-formulas specific steps
# link formula service pillars
if [ -n "$RECLASS_BASE" -a -e "$FORMULAS_BASE/$repo/metadata/service" ]; then
test -e $RECLASS_BASE/classes/service || mkdir -p $RECLASS_BASE/classes/service
ln -svf $FORMULAS_BASE/$repo/metadata/service $RECLASS_BASE/classes/service/$name
fi
# install dependencies
FETCHED+=($name)
if [ -e "$FORMULAS_BASE/$repo/metadata.yml" ]; then
fetchDependencies "$FORMULAS_BASE/$repo/metadata.yml"
fi
else
echo -e "[I] Formula "$name" already fetched."
fi
fi
else
echo -e '[I] Usage: fetchGitFormula git_repo_uri [branch] [local formula directory name]'
fi
}
# DEPRECATED, kept for backward compatibility
# for github.com/salt-formulas (linking "service" pillar metadata from formula to reclas classes)
function linkFormulas() {
# OPTIONAL: Link formulas from git/pkg
SALT_ROOT=$1
SALT_ENV=${2:-/usr/share/salt-formulas/env}
# form git, development versions
find "$SALT_ENV"/_formulas -maxdepth 1 -mindepth 1 -type d -print0| xargs -0 -n1 --no-run-if-empty basename | xargs -I{} --no-run-if-empty \
ln -fs "$SALT_ENV"/_formulas/{}/{} "$SALT_ROOT"/{};
# form pkgs
find "$SALT_ENV" -maxdepth 1 -mindepth 1 -path "*_formulas*" -prune -o -name "*" -type d -print0| xargs -0 -n1 --no-run-if-empty basename | xargs -I{} --no-run-if-empty \
ln -fs "$SALT_ENV"/{} "$SALT_ROOT"/{};
}
function setupPyEnv() {
MODULES="pygithub pyyaml"
pip3 install --upgrade $MODULES || {
which pipenv || {
pip install --upgrade pipenv
}
pipenv --three
pipenv install $MODULES
}
}
function listRepos_github_com() {
#export python=$(pipenv --py || (setupPyEnv &>/dev/null; pipenv --py))
if [ -e Pipfile.lock ]; then python=$(pipenv --py); else python=python3; fi
$python - "$1" <<-LIST_REPOS
import sys
import github
def make_github_agent(user=None, password=None):
""" Create github agent to auth """
if not user:
return github.Github()
else:
return github.Github(user, password)
def get_org_repos(gh, org_name):
org = gh.get_organization(org_name)
for repo in org.get_repos():
yield repo.name
try:
print(*get_org_repos(make_github_agent(), str(sys.argv[1])), sep="\n")
except Exception as e:
print("[E] {}".format(e.__doc__))
print("[E] {}".format(e.message))
sys.exit(1)
LIST_REPOS
}
function fetchAll() {
# iterate over all defined sources
for source in $(echo ${FORMULA_SOURCES} | xargs -n1 --no-run-if-empty); do
hosting=$(echo ${source//\./_} | awk -F'/' '{print $3}')
orgname=$(echo ${source//\./_} | awk -F'/' '{print $4}')
# Get repos. To protect builds on master we likely fail on errors/none while getting repos from $source
set -o pipefail
repos="$(listRepos_$hosting "$orgname" | xargs -n1 --no-run-if-empty| sort)"
set +o pipefail
if [ ! -n "$repos" ]; then
echo "[E] Error caught or no repositories found at $source. Exiting.";
exit 1;
fi
# fetch all repos that looks like formula
for repo in $(echo ${repos} | xargs -n1 --no-run-if-empty); do
# TODO, avoid a hardcoded pattern to filter formula repos
if [[ $repo =~ ^(.*formula.*)$ ]]; then
fetchGitFormula "$source/$repo";
fi
done;
done;
}
# detect if file is being sourced
[[ "$0" != "$BASH_SOURCE" ]] || {
# if executed, fetch specific formula
fetchGitFormula ${@}
}