From 8f31113bdfe0951d8576b517954bf3ccb4f5c457 Mon Sep 17 00:00:00 2001 From: caes Date: Thu, 21 May 2020 08:08:44 -0400 Subject: [PATCH] init --- .gitignore | 1 + README.md | 89 +++ broker.sh | 1531 ++++++++++++++++++++++++++++++++++++++++++++++++++ data-tool.js | 191 +++++++ data.json | 200 +++++++ package.json | 19 + parser.js | 274 +++++++++ test/test.js | 21 + 8 files changed, 2326 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 broker.sh create mode 100755 data-tool.js create mode 100644 data.json create mode 100644 package.json create mode 100644 parser.js create mode 100644 test/test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59e89a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +errors diff --git a/README.md b/README.md new file mode 100644 index 0000000..de13534 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# Adamocomp Data Parser +This library provides methods to interpret the adamocomp data file. +## Example Usage +``` javascript +const Parser = require('adamocomp/parser.js'); + +var parser = new Parser(); + +parser.get.data('gitlab repo').then(function(data){ + // "data" contains all data for the gitlab deal +}) + +parser.get_formatted.data('gitlab repo').then(function(printable){ + // Standard output as stringified json. + console.log(printable) +}) + +// parser.get_formatted.root('gitlab').then(function(str){ +// console.log(str) +// } +// Output below +``` + +``` bash +node data_tool.js path "gitlab" +# ["gitlab"] +``` +## Constructor Options +* `new Parser({options})` + +| property | type | description | default | +|---|---|---|---| +| path | string | Path to data file | `${program_root}/data.json` | +## Methods +### `get.file()` +Type: async + +Params: none + +Returns: an object literal of the data file. +``` javascript +parser.get.file().then(function(file){ + // 'file' is the entire data file +}) +``` +### `get.data(string)` + +Type: async + +| param | type | description | +|---|---|---| +| 1 | string | search string | + +Returns: data tree underneath node corresponding to search string, if found, otherwise null. +``` javascript +parser.get.data(str).then(function(data){ + console.log(JSON.stringify(data)); +}) +``` +### `get.root(string)` + +Type: async + +| param | type | description | +|---|---|---| +| 1 | string | search string | + +Returns: array of keys (directories) leading to deal, if found, or an empty array. +``` javascript +parser.get.root(str).then(function(nodes){ + console.log(locations); + // ['gitlab'] +}) +``` +### `get.list()` + +Type: async + +Params: none + +Returns: array of all deployments in the environment. +``` javascript +parser.get.list().then(function(deployments){ + console.log(location); + // ['gitlab', 'factorio/clusterio_host', 'factorio/factorio_host', etc.] +}) +``` +## Running Tests +`npm test` diff --git a/broker.sh b/broker.sh new file mode 100755 index 0000000..8de053f --- /dev/null +++ b/broker.sh @@ -0,0 +1,1531 @@ +#!/usr/bin/env bash + +# WARNING: functions do not test for permissions (mostly). + +print_help () { + echo "Usage: ./broker.sh [] []" + echo "" + echo "Options: " + echo " -t, --testing Report operations without executing them." + echo " -e, --env Set the environment variables from data." + echo "" + echo "Commands: " + print_help_item\ + "info "\ + "Collects all information for deal and prints it." + print_help_item\ + "complete_local "\ + "Updates and installs e.g. gitlab" + print_help_item\ + "stop_container "\ + "Stops a single container, e.g. 'gitlab'" + print_help_item\ + "restart_all_containers []"\ + "Restarts all containers or all containers under root dir." + print_help_item\ + "stop_all_containers []"\ + "Stops all containers or all containers under root dir." + print_help_item\ + "setup_local"\ + "Installs environment data and software requirements locally." + print_help_item\ + "install_on_nodes "\ + "Installs fresh deal across nodes in range." + print_help_item\ + "remote_node_setup "\ + "Remotely updates adamocomp on nodes." +} + +print_help_item () { + printf ' %-69.69s \n\t\t %-61.61s \n\n' "$1" "$2" +} + +# These positional arguments get run at the end of the script. +POSITIONAL=() +# These options get passed to brokers on remote systems. +PASS_OPTS=() +while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + -t|--testing) + TESTING_MODE="TRUE" + PASS_OPTS+=("-t") + shift # past option + ;; + # Set environment variables in data when running commands. + -e|--env) + SET_ENV="TRUE" + PASS_OPTS+=("-e") + shift + ;; + -h|--help) + print_help + exit 0 + ;; + -l|--lib) + LIBPATH="$2" + shift # past argument + shift # past value + ;; + # Don't use. + -d|--debug) + DEBUG=TRUE + shift # past argument + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; + esac +done + +###################################################################### +# Directory and config structure + +program_root="$(\ + cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd\ +)" +stage_root="${program_root}/staging" +target_root=${ADAMOCOMP_HOME:-"/"} +config_dir=${ADAMOCOMP_CONFDIR:-"${program_root}"} + +if [[ "$TESTING_MODE" == "TRUE" ]]; then + mkdir -p "${program_root}/errors" + errfile="${program_root}/errors/`date '+%D.%H:%M:%S'| + sed 's|/|.|g'`" +else + errfile="/dev/null" +fi + + +# Non-public configurations; eventually should be off-loaded to Vault. +data_file="${config_dir}/data.json" +data_json="$(jq '.' ${data_file} 2>>$errfile)" + +if [[ -r "${program_root}/data.json" ]]; then + data_file="${program_root}/data.json" + data_json="$(jq '.' ${data_file} 2>>$errfile)" +fi + +###################################################################### +# Data interface + +datatool="${program_root}/data-tool.js -p ${data_file}" +editor="/usr/bin/nano" + +if [[ $DEBUG == "TRUE" ]]; then + datatool="${datatool} -d" +fi + +user_edit () { + $editor "$@" +} + +get_filemode () { + stat -c '%a' $1 +} + +option_str_from_arr () { + opt_str="" + flag="--publish" + options=("443:443" "80:80" "22:22") + for option in ${options[@]}; do + opt_str="${opt_str} ${flag} ${option} " + done +} + +jq_array_size () { + json=`cat` + size=$(echo $json|jq 'length' 2>>$errfile) + if [[ size == "" ]]; then + echo 0 + return 1 + fi + echo $size + return +} + +json_to_bash_array () { + local json=`cat` + if jq -e . >/dev/null 2>&1 <<<"$json"; then + echo $json|jq -cr '.[]' + else + echo "" + fi + return +} + +data_array_size () { + get_data $*|jq_array_size +} + +data_array_seq_end () { + echo $(( $(data_array_size $*) - 1 )) +} + +get_data_array () { + get_data "$*"|jq -cr '.[]' +} + +get_data () { + ${datatool} get "$*"|jq -r '.[0]' +} + +get_keys () { + ${datatool} get "$*"|jq -r '.[0]|keys' +} + +get_key () { + get_keys "${@:2}"|jq -r ".[$1]" +} + +count_keys () { + get_keys "$*"|jq_array_size +} + +get_path () { + ${datatool} path "$*"|jq -r '.[0]' +} + +get_cluster_size () { + get_hosts "$*"|jq_array_size +} + +get_hosts () { + ${datatool} hosts "$*"|jq '.[0]' +} + +get_hosts_array () { + get_hosts "$*"|json_to_bash_array +} + + + #for i in `seq 1 $cluster_size`; do + #local host_name=$(get_key $((i-1)) $cluster_name) + +deal_exists () { + ${datatool} valid_deal "$*" +} + +cluster_exists () { + ${datatool} valid_cluster "$*" +} + +host_exists () { + ${datatool} valid_host "$*" +} + +get_remote_host () { + if host_exists $*; then + get_data $* remote host + fi +} + +get_remote_ssh_port () { + if host_exists $*; then + get_data $* remote ports ssh + fi +} + +###################################################################### +# Formatted printing and debug. + +# Prints a table of variable names and values to stdout. +pr_vars () { + printf "%s \n" "----------------------------" + pr_vars_no_break "$@" +} + +pr_vars_no_break () { + for k in "$@"; do + len=$(echo "${!k}"|wc -m) + if [[ len -gt 50 ]]; then + begin=$(( len - 51 )) + val=$(expr substr "${!k}" $begin $len ) + else + val="${!k}" + fi + printf '%-15.15s : %.51s \n' "$k" "${val[*]}" + done +} + +# Debug that prints relevant environment information. +print_env () { + pr_vars TESTING_MODE errfile + pr_vars program_root config_dir logger datatool + pr_vars stage_root target_root + pr_vars data_file + pr_vars dealer dealermail + pr_vars github_url github_owner github_token +} + +# Takes a json array and prints it as a flat string. +strip_json_formatting () { + echo $@| + sed 's/[",\]//g'| + sed 's/\[//g'| + sed 's/\]//g'| + sed 's/{//g'| + sed 's/}//g' +} + +format_container_name () { + if [[ $2 =~ [0-9] ]]; then + echo "${1}-${2}" + else + echo "${2}" + fi + return +} + +concat_paths () { + if [[ "${2}" == "null" ]] || [[ -z "${2}" ]]; then + echo "${1}" + else + local result="${1}/${2}" + local roots=$( + awk -F"\\\.\\\." '{print NF-1}' <<< "${2}") + if [[ "$roots" -gt 0 ]]; then + result=$(dirname "$result") + for i in `seq ${roots}`; do + result=$(dirname "$result") + done + fi + echo "$result"|sed 's|/./\+|/|g'|sed 's|//\+|/|g' + fi + return +} + +###################################################################### +# Environment Setup + +dealer='caes' +dealermail='caes@adamonet' + +logtool="${syslog_dir}/bash-logger/syslog.sh" + +export_env () { + local lines=() + readarray -t lines < <(get_data_array etc environment) + for line in "${lines[@]}"; do + if [[ $TESTING_MODE == "TRUE" ]]; then + echo export $line + fi + export $line + done +} + +install_environment_vars () { + : + # Should check /etc/environment and add lines + # only if they don't already exist. + # Need to write a function to do this for a file + # in the general case to proceed. +} + +set_npm_proxy () { + : + #npm config set proxy http://some.proxy:83 + #npm config set https-proxy http://some.proxy:83 +} + +export_completions () { + : +} + +update_data () { + git_update_self $1 +} + +install_logger () { + echo "Logger not currently installed". +# rm -rf\ +# ADAMONET-bash-logger-*\ +# bash-logger\ +# bash-logger.tar\ +# 2> /dev/null +# get_release bash-logger +# tar xf bash-logger.tar +# rm bash-logger.tar +# mv ADAMONET-bash-logger-* bash-logger +# return +} + +syslog () { + ${logtool} "[adamocomp] ""$@" +} + +if [[ "$TESTING_MODE" == "TRUE" ]]; then + syslog_local="TRUE" +fi + +apt_update () { + sudo apt update -y +} + +install_prereqs () { + if [[ ! -x /usr/bin/jq ]]; then + sudo apt install jq + fi + if [[ ! -x /usr/bin/logger ]]; then + sudo apt install logger + fi + if [[ ! -x /usr/bin/curl ]]; then + sudo apt install curl + fi + if [[ ! -x /usr/bin/curl ]]; then + sudo apt install curl + fi + if [[ ! -x /usr/bin/node ]]; then + curl -sL https://deb.nodesource.com/setup_14.x | bash - + sudo apt install -y nodejs npm + fi +} + +setup_env () { + owd=`pwd` + cd $program_root + #set_npm_proxy + update_data $1 + install_logger + npm up + cd $owd +} + +setup_local () { + setup_env $* +} + +###################################################################### +# Global constructions. + +dofor_deal_info () { + local command="" + case "$*" in + "u"*) command="unset" ;; + "p"*) command="pr_vars" ;; + esac + if [[ $command != "" ]]; then + ${command} deal_valid deal_node parent deal_name issue_path + ${command} use_tools toolset missing_tools + ${command} deal_repo deal_branch deal_tag use_npm + ${command} docker_image docker_options + ${command} tool_path tool_src + ${command} stage_node close_node stage_path close_path + ${command} tool_target tool_stage + fi +} + +clear_deal_info () { + dofor_deal_info "unset" +} + +pr_deal_info () { + dofor_deal_info "pr_vars" +} + +# might as well run this at runtime + +clear_deal_info + +construct_deal_info () { + clear_deal_info + if deal_exists "$*"; then + deal_node=$(get_path $*) + parent=$(basename $(dirname $deal_node)) + deal_name=$(basename $deal_node) + issue_path=$(get_data "$parent $deal_name issue_path") + + toolset=$(get_data "$parent $deal_name toolset") + tool_path=$(get_data "$parent $deal_name tool_path") + tool_src="${program_root}/toolsets/${toolset}" + + docker_image=$(get_data "$parent $deal_name docker image") + docker_options=$(get_data "$parent $deal_name docker options") + + deal_repo=$(get_data "$parent $deal_name repo"| + sed 's/.git$//'|sed 's\^ADAMONET/\\' + ) + deal_branch=$(get_data "$parent $deal_name branch") + deal_tag=$(get_data "$parent $deal_name tag") + use_npm=$(get_data "$parent $deal_name npm_setup") + + + stage_node=$(concat_paths $stage_root $deal_node) + close_node=$(concat_paths $target_root $deal_node) + stage_path=$(concat_paths $stage_node $issue_path) + close_path=$(concat_paths $close_node $issue_path) + tool_target=$(concat_paths $close_node $tool_path) + tool_stage=$(concat_paths $stage_node $tool_path) + + if [[ $tool_path == "null" ]] || [[ -z $tool_path ]]; then + unset use_tools missing_tools + else + use_tools="TRUE" + if ls ${tool_target}/*.sh >> /dev/null 2> /dev/null; + then + unset missing_tools + else + missing_tools="TRUE" + fi + fi + deal_valid="TRUE" + else + return 2 + fi + return 0 +} + + +clear_host_list () { + unset cluster_name cluster_size + host_list=() +} + +push_host () { + host=$1 + host_list+=("$host") +} + +pr_host_list () { + pr_vars cluster_name + for host in "${host_list[@]}"; do + local fqdn=$(get_remote_host $host) + local ssh_port=$(get_remote_ssh_port $host) + pr_vars host fqdn ssh_port + done +} + +# This fills in fqdn_list using arguments. +construst_cluster_info () { + clear_host_list + + cluster_name=$1;shift + + if [[ ! $cluster_name =~ ^[a-zA-Z]+$ ]]; then + >&2 echo "Cluster argument needs to be string." + return 12 + fi + + if cluster_exists $cluster_name; then + cluster_size=$(get_cluster_size $cluster_name) + local hosts=() + readarray -t hosts < <(get_hosts_array $cluster_name) + if [[ ${#hosts[@]} -lt 2 ]] && [[ -z ${hosts[0]} ]]; then + push_host "$cluster_name" + else + for host in "${hosts[@]}"; do + push_host "$host" + done + fi + return 0 + else + >&2 echo "Invalid cluster." + return 2 + fi +} + +###################################################################### +# Informations + +info () { + construct_deal_info $* + pr_deal_info + return +} + +cluster_info () { + construst_cluster_info $* + pr_host_list +} + +###################################################################### +# Downloading releases/clones from git repos. + +github_url="$(echo $githubenv_json|jq -r '.base_url')" +github_owner="$(echo $githubenv_json|jq -r '.repo_owner')" +github_token="$(echo $githubenv_json|jq -r '.access_token')" + +git_identify_global () { + git config --global user.name "${dealer}" + git config --global user.email "${dealermail}" +} + +git_update_self () { + owd=`pwd` + cd ${program_root} + git_identify_global + git stash + git fetch + if [[ ! -z $1 ]]; then + git checkout $1 + else + git checkout master + fi + git merge + cd ${owd} +} + +# Prints json object of repo release. +get_release_data () { + local repo="${1}" + local release="${2}" + local owner_path="${github_url}/repos/${github_owner}" + local repo_path="${owner_path}/${repo}/releases/${release}" + curl -s \ + -H "Authorization: token ${github_token}"\ + "${repo_path}" + #|jq -r +} + +# Retrieves latest release of deal. +# Usage: get_release [] +# Downloads release tarball as ".tar". +get_release () { + local repo="${1}" + local release="${2:-latest}" + local release_data=$(get_release_data "$repo" "$release") + local tarball_url=$(echo "$release_data"|jq -r '.tarball_url') + + if [[ "$TESTING_MODE" == "TRUE" ]]; then + pr_vars repo release tarball_url release_data + else + curl -s \ + -H "Authorization: token ${github_token}" \ + -L -o "${1}.tar" $tarball_url + fi +} + +# Retrieves clone of branch in repo. +# Usage: get_clone +# Extracts clone to directory of same name, I guess. +# Needs to be changed to use token authentication. +get_clone () { + local repo="${1}" + local branch="${2:-master}" + if [[ "$TESTING_MODE" == "TRUE" ]]; then + pr_vars repo branch + return 0 + else + github_root="${github_token}@gitlab.adamonet" + git clone \ + --single-branch \ + --branch $branch \ + "https://${github_root}/${github_owner}/${repo}.git" + return + fi +} + +###################################################################### +# Container operations. + +docker_options_string () { + num_opt=$(data_array_seq_end ${deal_name} docker options) + for i in `seq 0 $num_opt`; do + num_args=$(data_array_seq_end ${deal_name} docker options $i) + for j in `seq 1 $num_args`; do + printf "%s "\ + "$(get_data ${deal_name} docker options $i 0)" + printf "%s "\ + "$(get_data ${deal_name} docker options $i $j)" + done + done + return +} + +build_container () { + if [[ ! -x $target_root ]]; then + >&2 echo "Cannot access root at ${target_root}." + return 2 + fi + if deal_exists "$*"; then + local deal_node=`${datatool} path "$*"|jq -r '.[0]'` + local parent=$(basename $(dirname $deal_node)) + local node=$(basename $deal_node) + local issue_path=$(get_data "$parent $node issue_path") + local name=$(format_container_name $parent $node|sed 's/-/:/') + local build_node="${target_root}/${deal_node}" + local build_dir=$(concat_paths $build_node $issue_path) + local repo=$(get_data "$parent $name repo"|jq -r '.[0]') + + pr_vars name repo build_dir + + if [[ "$TESTING_MODE" == "TRUE" ]]; then + pr_vars deal_node parent node issue_path \ + name build_node build_dir repo + return 0 + fi + + if DOCKER_BUILDKIT=1 docker build \ + --ssh id_rsa=~/.ssh/id_rsa --no-cache \ + --tag ${name} ${build_dir} + then + syslog "Image $name rebuilt from $deal_repo." + else + syslog "Error rebuilding image $name." + fi + return + else + >&2 echo "Deal not found." + return 10 + fi +} + +# start_container [