diff options
-rw-r--r-- | .app/lib/app-conf | 205 | ||||
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | NOTES.tmp | 16 | ||||
-rw-r--r-- | README.md (renamed from docs/README.md) | 0 | ||||
-rwxr-xr-x | app | 131 | ||||
-rwxr-xr-x | bin/app-conf | 135 | ||||
-rwxr-xr-x | bin/app-init | 79 | ||||
-rwxr-xr-x[-rw-r--r--] | bin/app-instance (renamed from .app/lib/app-instance) | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | bin/app-operate (renamed from .app/lib/app-operate) | 8 | ||||
-rwxr-xr-x | bin/pid-method (renamed from .app/lib/pid-method) | 0 | ||||
-rw-r--r-- | lib/common (renamed from .app/lib/app-common) | 76 | ||||
-rw-r--r-- | lib/default-config | 1 | ||||
-rwxr-xr-x | libexec/app-cat-conf | 47 | ||||
-rwxr-xr-x | libexec/app-grep-path | 13 | ||||
-rwxr-xr-x | libexec/app-install-file | 172 | ||||
-rwxr-xr-x | libexec/app-resolver-maven | 231 | ||||
-rwxr-xr-x | test/app-cat-conf.bats | 56 | ||||
-rwxr-xr-x | test/app-common.bats | 14 | ||||
-rwxr-xr-x | test/app-conf.bats | 165 | ||||
-rwxr-xr-x | test/app-init.bats | 42 | ||||
-rwxr-xr-x | test/app-install.bats | 9 | ||||
-rw-r--r-- | test/data/app-cat-conf/config-1 | 7 | ||||
-rw-r--r-- | test/data/app-cat-conf/config-2 | 1 | ||||
-rw-r--r-- | test/data/app-cat-conf/config-3 | 4 | ||||
-rwxr-xr-x | test/data/app-common/app-bar | 0 | ||||
-rwxr-xr-x | test/data/app-common/app-faz | 0 | ||||
-rwxr-xr-x | test/data/app-common/app-foo | 0 | ||||
-rw-r--r-- | test/utils.bash | 78 |
28 files changed, 1087 insertions, 423 deletions
diff --git a/.app/lib/app-conf b/.app/lib/app-conf deleted file mode 100644 index 8149e41..0000000 --- a/.app/lib/app-conf +++ /dev/null @@ -1,205 +0,0 @@ -#!/bin/bash - -# TODO: Add a 'get' method that returns a single value -# Exit with 0 if found, 1 otherwise. - -key_expr="[a-zA-Z][_a-zA-Z0-9]*\.[a-zA-Z][_a-zA-Z0-9]*" - -format_conf() { - local IFS== - while read key value - do - printf "%-20s %-20s" "$key" "$value" - echo - done -} - -get_conf() { - local apps=$1 - local name=$2 - local instance=$3 - local key=$4 - local default= - local file=$apps/$name/$instance/current/etc/app.conf - - shift 4 - - if [ $# -gt 0 ] - then - default=$1 - shift - fi - - if [ ! -r $file ] - then - echo "$default" - return 0 - fi - - value=`sed -n "s,^${key}[ ]*=[ ]*\(.*\)$,\1,p" $file` - - if [ -z "$value" ] - then - echo "$default" - fi - - echo "$value" -} - -get_conf_all() { - local apps=$1 - local name=$2 - local instance=$3 - local file=$apps/$name/$instance/current/etc/app.conf - - if [ ! -r "$file" ] - then - return 0 - fi - - sed -n "s,^[ ]*\($key_expr\)[ ]*=[ ]*\(.*\)$,\1=\2,p" "$file" | sort -} - -get_conf_in_group() { - local apps=$1 - local name=$2 - local instance=$3 - local group=$4 - - get_conf_all "$apps" "$name" "$instance" | sed -n "s,^${group}\.\(.*\),\1,p" -} - -assert_key() { - key=$1 - - local x=`echo $key | sed -n "/^$key_expr$/p"` - if [ -z "$x" ] - then - echo "Invalid key: $key" >&2 - exit 1 - fi -} - -conf_set() { - local apps=$1 - local name=$2 - local instance=$3 - local key=$4 - local value=$5 - - local file=$apps/$name/$instance/current/etc/app.conf - - assert_key "$key" - - if [ -r $file ] - then - sed "/^$key[ ]*=.*/d" $file > $file.tmp - fi - - echo "$key=$value" >> $file.tmp - mv $file.tmp $file -} - -conf_delete() { - local apps=$1 - local name=$2 - local instance=$3 - local key=$4 - - local file=$apps/$name/$instance/current/etc/app.conf - - assert_key "$key" - - sed "/^$key[ ]*=.*/d" $file > $file.tmp - mv $file.tmp $file -} - -method_conf_list() { - local name=$1; shift - local instance=$1; shift - - get_conf_all "$apps" "$name" "$instance" | format_conf -} - -method_conf_usage() { - if [ -n "$1" ] - then - echo "Error:" $@ >&2 - fi - - echo "usage: $0 conf <method>" >&2 - echo "" - echo "Available methods:" >&2 - echo " list - list all configuration values" >&2 - echo " list-group [group] - list all configuration values in the specified group" >&2 - echo " set [group.key] [value] - set a configuration parameter" >&2 - echo " delete [group.key] - deletes a configuration parameter" >&2 - exit 1 -} - -method_conf() { - local name="$1"; shift - local instance="$1"; shift - local method="$1" - - if [ $# -gt 0 ] - then - shift - fi - - assert_is_instance method_conf_usage "$name" "$instance" - - case "$method" in - list) - if [ $# -gt 0 ] - then - method_conf_usage "Extra options." - exit 1 - fi - - method_conf_list "$name" "$instance" - ;; - list-group) - if [ $# -ne 1 ] - then - method_conf_usage - exit 1 - fi - - get_conf_in_group "$apps" "$name" "$instance" "$1" | format_conf - ;; - set) - if [ $# -ne 2 ] - then - method_conf_usage - exit 1 - fi - - conf_set "$apps" "$name" "$instance" "$1" "$2" - ;; - delete) - if [ $# -ne 1 ] - then - method_conf_usage - exit 1 - fi - - conf_delete "$apps" "$name" "$instance" "$1" - ;; - *) - if [ $# -eq 0 ] - then - method_conf_list "$name" "$instance" - exit 0 - fi - - if [ -z "$method" ] - then - method_conf_usage - else - method_conf_usage "Unknown method $method" - fi - exit 1 - ;; - esac -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2019e13 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: test + +BATS=$(patsubst test/%,%,$(wildcard test/*.bats)) +TESTS=$(addprefix bats-,$(BATS)) + +bats-%: + @echo === test/$(patsubst bats-%,%,$@) + @bats test/$(patsubst bats-%,%,$@) + +test: $(TESTS) + @echo BATS=$(BATS) + @echo TESTS=$(TESTS) + +.PHONY: test diff --git a/NOTES.tmp b/NOTES.tmp new file mode 100644 index 0000000..1173e32 --- /dev/null +++ b/NOTES.tmp @@ -0,0 +1,16 @@ +app init maven io.trygvis.appsh.examples:foo:1.0-SNAPSHOT => + + # find resolver + app config set app.resolver maven + # run resolver/maven + # resolve version for artifact + # download artifact + app config set maven.id io.trygvis.appsh.examples:foo:1.0-SNAPSHOT + # run scripts/ + # create symlink + +app init [-d directory] <resolver> <resolver args> + +resolver/maven -r <repository url> <artifact id> + +app list - lists all applications available under the specified directory diff --git a/docs/README.md b/README.md index c4a83e9..c4a83e9 100644 --- a/docs/README.md +++ b/README.md @@ -1,6 +1,4 @@ -#!/bin/bash - -set -e +#!/bin/bash -e PRG="$0" while [ -h "$PRG" ] ; do @@ -16,113 +14,56 @@ done APPSH_HOME=`dirname "$PRG"` APPSH_HOME=`cd "$APPSH_HOME" && pwd` -# Not sure this is useful -#if [ -z "$APPSH_APPS" ] -#then -# apps=`dirname $0` -# apps=`cd $apps; pwd` -#fi - -apps=`dirname $0` -apps=`cd $apps; pwd` - -# Ideally this should just do "cd /" to ensure that all paths are useful. -cd $apps - -mkdir -p $apps/.app/var/pid -mkdir -p $apps/.app/var/download - -method_usage() { +usage() { if [ -n "$1" ] then echo "Error:" "$@" >&2 fi - echo "usage: $0 [-n name] [-i instance] <method group>" >&2 + echo "usage: $0 <command>" >&2 echo "" >&2 - echo "Available method groups:" >&2 - echo " instance" >&2 + echo "Available commands:" >&2 + echo " init" >&2 echo " conf" >&2 echo " operate" >&2 echo "" >&2 echo "Run $0 -h <group> for more help" >&2 } -. $APPSH_HOME/.app/lib/app-common -. $APPSH_HOME/.app/lib/app-instance -. $APPSH_HOME/.app/lib/app-conf -. $APPSH_HOME/.app/lib/app-operate +. $APPSH_HOME/lib/common -main() { - local method - local name="$APPSH_NAME" - local instance="$APPSH_INSTANCE" +while getopts "h" opt +do + case $opt in + h) + usage + exit 1 + ;; + \?) + usage + exit 1 + ;; + esac +done - while getopts "n:i:h" opt - do - case $opt in - n) - name=$OPTARG - shift 2 - OPTIND=1 - ;; - i) - instance=$OPTARG - shift 2 - OPTIND=1 - ;; - h) - shift - OPTIND=1 - h="$1" +if [ $# -eq 0 ] +then + usage +fi - if [ -z "$h" ] - then - method_usage - else - case "$h" in - instance) - method_instance_usage - ;; - conf) - method_conf_usage - ;; - operate) - method_operate_usage - ;; - *) - echo "No such method group: $h" - ;; - esac - fi - exit 1 - ;; - \?) - echo "Invalid option: $OPTARG" - ;; - esac - done +command=$1; shift - local method=$1 - if [ $# -gt 0 ] - then - shift - fi +bin=`grep_path "/app-$command$" "$APPSH_HOME/bin"` - case "$method" in - instance) method_instance "$name" "$instance" "$@" ;; - conf) method_conf "$name" "$instance" "$@" ;; - operate) method_operate "$name" "$instance" "$@" ;; - *) - if [ -z "$method" ] - then - method_usage - else - method_usage "No such method group: $method" - fi - ;; - esac - exit $? -} +if [ ! -x "$bin" ] +then + echo "Unknown command: $command" 2>&1 + exit 1 +fi + +PATH=$APPSH_HOME/bin:$PATH -main "$@" +# TODO: this is probably a good place to clean up the environment +exec env \ + "APPSH_HOME=$APPSH_HOME" \ + "$bin" "$@" diff --git a/bin/app-conf b/bin/app-conf new file mode 100755 index 0000000..7193c20 --- /dev/null +++ b/bin/app-conf @@ -0,0 +1,135 @@ +#!/bin/bash + +# TODO: Add a 'get' command that returns a single value +# Exit with 0 if found, 1 otherwise. + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +. $APPSH_HOME/lib/common + +PATH=$APPSH_HOME/libexec:$PATH + +key_expr="[a-zA-Z][_a-zA-Z0-9]*" + +format_conf() { + local IFS== + while read key value + do + printf "%-20s %-20s" "$key" "$value" + echo + done +} + +assert_valid_config_name() { + local name=$1 + + local x=`echo $name | sed -n "/^$key_expr\\.$key_expr$/p"` + if [ -z "$x" ] + then + echo "Invalid name: $name" >&2 + exit 1 + fi +} + +conf_set() { + local name=$1; shift + local value=$1; shift + + assert_valid_config_name "$name" + + if [ -r $file ] + then + sed "/^$name[ ]*=.*/d" $file > $file.tmp + fi + + echo "$name=$value" >> $file.tmp + mv $file.tmp $file +} + +conf_delete() { + local name=$1; shift + + assert_valid_config_name "$name" + + sed "/^$name[ ]*=.*/d" $file > $file.tmp + mv $file.tmp $file +} + +usage() { + if [ -n "$1" ] + then + echo "Error: $@" >&2 + fi + + echo "usage: $0 conf <command>" >&2 + echo "" + echo "Available commands:" >&2 + echo " list - list all config values" >&2 + echo " set [name] [value] - set a config parameter" >&2 + echo " delete [name] - deletes a config parameter" >&2 + exit 1 +} + +if [ $# -gt 0 ] +then + command=$1 + shift +else + command=list +fi + +file=".app/config" + +assert_is_app -C + +case "$command" in + get) + if [ $# != 1 ] + then + usage + exit 1 + fi + + app-cat-conf -f "$file" -n "$1" | cut -f 2 -d = | format_conf | sed "s, *$,," + ;; + list) + if [ $# -gt 0 ] + then + usage "Extra options." + exit 1 + fi + + app-cat-conf -f "$file" | format_conf + ;; + set) + if [ $# -ne 2 ] + then + usage + exit 1 + fi + + conf_set "$1" "$2" + ;; + delete) + if [ $# -ne 1 ] + then + usage "Missing [name] argument." + exit 1 + fi + + conf_delete "$1" "$2" + ;; + *) + if [ -z "$command" ] + then + usage + else + usage "Unknown command: $command" + fi + exit 1 + ;; +esac diff --git a/bin/app-init b/bin/app-init new file mode 100755 index 0000000..e758916 --- /dev/null +++ b/bin/app-init @@ -0,0 +1,79 @@ +#!/bin/bash -e + +set -u + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +. $APPSH_HOME/lib/common + +usage() { + echo "usage: $0 -d dir <resolver> <resolver args>" + exit 1 +} + +fatal() { + echo "$0: $@" + exit 1 +} + +while getopts "d:" opt +do + case $opt in + d) + dir=$OPTARG + shift 2 + OPTIND=1 + ;; + esac +done + +if [ $# -lt 1 ] +then + usage +fi + +resolver_name="$1"; shift + +if [ -z "$dir" ] +then + usage +fi + +if [ -e "$dir" ] +then + fatal "Already initialized: $dir" 2>&1 +fi + +# TODO: install a trap handler and rm -rf "$dir" + +resolver=`grep_path "/app-resolver-$resolver_name$" "$PATH:$APPSH_HOME/libexec" | head -n 1` + +if [ -z "$resolver" ] +then + echo "No such resolver: $resolver_name" 2>&1 + exit 1 +fi + +mkdir -- "$dir" "$dir/.app" +cd "$dir" + +app-conf set app.resolver "$resolver_name" + +"$resolver" init "$@" +"$resolver" resolve-version + +version=`app-conf get app.version` + +if [[ $version == "" ]] +then + echo "Unable to resolve version" 2>&1 + exit +fi + +echo "Resolved version to $version" + +"$resolver" download-version -v "$version" -f .app/latest.zip diff --git a/.app/lib/app-instance b/bin/app-instance index 25983f0..02e3c0f 100644..100755 --- a/.app/lib/app-instance +++ b/bin/app-instance @@ -1,5 +1,11 @@ #!/bin/bash +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + if [ -n "$APPSH_REPO" ] then repo="$APPSH_REPO" diff --git a/.app/lib/app-operate b/bin/app-operate index 2b1408d..dc16780 100644..100755 --- a/.app/lib/app-operate +++ b/bin/app-operate @@ -1,5 +1,11 @@ #!/bin/bash +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + operate_usage() { if [ -n "$1" ] then @@ -35,7 +41,7 @@ method_operate() { shift fi - bin=`get_conf $apps $name $instance app.method` + bin=`$APPSH_HOME/bin/app-cat-conf -f $apps/$name/$instance/current/etc/app.conf -g app -k method | cut -f 2 -d =` if [ -z "$bin" ] then diff --git a/.app/lib/pid-method b/bin/pid-method index 29f6b4f..29f6b4f 100755 --- a/.app/lib/pid-method +++ b/bin/pid-method diff --git a/.app/lib/app-common b/lib/common index fabdad4..22c8cd0 100644 --- a/.app/lib/app-common +++ b/lib/common @@ -1,37 +1,26 @@ #!/bin/bash -assert_is_instance() { - local usage=$1 - local name=$2 - local instance=$3 - local check_link=$4 +assert_is_app() { + local check_link=yes - if [ -z "$name" ] - then - $usage "Missing required option -n." - fi - - if [ -z "$instance" ] - then - $usage "Missing required option -i." - fi - - # Use $APPSH_APPS as prefix if it's set - local x="" - if [ ! -z "$APPSH_APPS" ] - then - x="$APPSH_APPS/" - fi + while getopts "C" opt + do + case $opt in + C) + check_link=no + ;; + esac + done - if [ ! -d $x$name/$instance ] + if [ ! -d .app ] then - echo "No such instance: $name/$instance." >&2 + echo "This is not an app, missing directory: '.app'" >&2 exit 1 fi - if [ "$check_link" != "no" ] + if [[ $check_link == yes ]] then - if [ ! -e $x$name/$instance/current ] + if [ ! -e current ] then echo "Missing 'current' link." >&2 exit 1 @@ -40,9 +29,9 @@ assert_is_instance() { } list_apps() { - filter_name=$1; shift - filter_instnace=$1; shift - vars="$@" + local filter_name=$1; shift + local filter_instnace=$1; shift + local vars="$@" sort $apps/.app/var/list | while read line do @@ -82,8 +71,8 @@ list_apps() { } find_current_version() { - name=$1 - instance=$2 + local name=$1 + local instance=$2 if [ ! -L $apps/$name/$instance/current ] then @@ -97,8 +86,8 @@ find_current_version() { } find_versions() { - name=$1 - instance=$2 + local name="$1" + local instance="$2" if [ ! -d $apps/$name/$instance/versions ] then @@ -111,6 +100,24 @@ find_versions() { ) } +grep_path() { + local regex="$1"; shift + local path="$1"; shift + + find `echo $path | tr : " "` -type f -executable 2>/dev/null | (egrep "$regex" || exit 0) +# IFS=: +# for x in $path +# do +# ls $x/* 2>/dev/null | while read f +# do +# if [[ $f =~ $regex ]] +# then +# echo $f +# fi +# done +# done +} + # TODO: set ulimit # TODO: set umask # TODO: change group newgrp/sg @@ -133,7 +140,8 @@ run_app() { APPSH_INSTANCE_HOME=`pwd` cd current - e="`get_conf_in_group $apps $name $instance env`" + local e=`$APPSH_HOME/bin/app-cat-conf -f $apps/$name/$instance/current/etc/app.conf -g env | cut -f 2- -d .` + #e="`get_conf_in_group $apps $name $instance env`" # This magically get the expansion of $u correct. IFS=" " @@ -143,7 +151,7 @@ run_app() { env -i \ PATH=/bin:/usr/bin \ $e \ - PWD=$PWD \ + PWD="$PWD" \ APPSH_METHOD=$method \ APPSH_APPS=$apps \ APPSH_HOME=$APPSH_HOME \ diff --git a/lib/default-config b/lib/default-config new file mode 100644 index 0000000..855cc8b --- /dev/null +++ b/lib/default-config @@ -0,0 +1 @@ +maven.repo=http://repo1.maven.org diff --git a/libexec/app-cat-conf b/libexec/app-cat-conf new file mode 100755 index 0000000..78d3c36 --- /dev/null +++ b/libexec/app-cat-conf @@ -0,0 +1,47 @@ +#!/bin/bash + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +set -e + +key_expr="[a-zA-Z][_a-zA-Z0-9]*" + +file=.app/config + +while getopts "f:n:" opt +do + case $opt in + f) + file=$OPTARG + ;; + n) + name=$OPTARG + ;; + \?) + echo "Invalid option: $OPTARG" >&2 + exit 1 + ;; + esac +done + +if [ -z "$name" ] +then + filter="s,^[ ]*\($key_expr\.$key_expr\)[ ]*=[ ]*\(.*\)$,\1=\2,p" +else + filter="s,^\($name\)=\(.*\),\1=\2,p" +fi + +if [[ $APPSH_DEFAULT_CONFIG == "" ]] +then + APPSH_DEFAULT_CONFIG=$APPSH_HOME/lib/default-config +fi + +# The awk script makes sure each key only appears once +cat "$file" "$APPSH_DEFAULT_CONFIG" | \ + sed -n -e "$filter" $extra | \ + awk -F = ' (!($1 in a)){a[$1]; print }' | \ + sort diff --git a/libexec/app-grep-path b/libexec/app-grep-path new file mode 100755 index 0000000..f5e8287 --- /dev/null +++ b/libexec/app-grep-path @@ -0,0 +1,13 @@ +#!/bin/bash + +# A command line wrapper around get grep_path function + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +. $APPSH_HOME/lib/common + +grep_path "$1" "$2" diff --git a/libexec/app-install-file b/libexec/app-install-file new file mode 100755 index 0000000..6c06aa7 --- /dev/null +++ b/libexec/app-install-file @@ -0,0 +1,172 @@ +#!/bin/bash + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +calculate_md5() { + local file="$1"; shift + + md5sum "$file" | cut -c 1-32 +} + +# TODO: support file:// repositories +# TODO: look in the local repository first +get() { + local url=$1 + local file=$2 + local exit + + curl -o $file $url -D curl.tmp + + exit=`grep "^HTTP/[0-9]\.[0-9] 200 .*" curl.tmp >/dev/null; echo $?` + head=`head -n 1 curl.tmp` + rm -f curl.tmp + if [ "$exit" != 0 ] + then + echo "Unable to download $url: $head" >&2 + exit 1 + fi +} + +resolve_snapshot() { + local groupId=$1; shift + local groupIdSlash=$1; shift + local artifactId=$1; shift + local version=$1; shift + + local metadata=$apps/.app/var/download/$groupId-$artifactId-$version-metadata.xml + local base_url=$repo/$groupIdSlash/$artifactId/$version + get $base_url/maven-metadata.xml $metadata + local resolved_version=`xmlstarlet sel -t -m '//snapshotVersion[extension[text()="zip"]]' -v value $metadata` + echo $resolved_version +} + +download_artifact() { + local file="$1"; shift + local url="$1"; shift + + echo "Downloading $url.md5" + get $url.md5 $file.md5 + local expected_md5="`cat $file.md5`" + + if [ -r $file ] + then + if [ "$expected_md5" == "`calculate_md5 $file`" ] + then + echo "Artifact already downloaded." + else + rm -f "$file" + fi + return 0 + fi + echo "Downloading artifact: $url" + get $url $file + + local actual_md5="`calculate_md5 $file`" + if [ "$expected_md5" == "$actual_md5" ] + then + echo "Artifact downloaded." + else + echo "Invalid checksum. Expected $expected_md5, got $actual_md5" >&2 + exit 1 + fi +} + +if [ $# -lt 2 ] +then + method_install_usage +fi + +case "$resolver" in + maven) + ;; + file) + if [ ! -r "$url" ] + then + echo "Could not read file: $url" >&2 + exit 1 + fi + + # TODO: should the zip file be copied into download/ so that + # there's always a local copy? + zip_file=$url + + if [ -z "$version" ] + then + version=`TZ=UTC date +"%Y%m%d-%H%M%S"` + fi + + resolved_version=$version + ;; + *) + method_install_usage "Invalid resolver type: $resolver" + ;; +esac + +if [ -d versions/$resolved_version ] +then + echo "Version $resolved_version is already installed" + exit 1 +fi + +mkdir -p versions/$resolved_version + +echo "Unpacking..." +unzip -q -d versions/$resolved_version $zip_file + +if [ ! -d versions/$resolved_version/root ] +then + echo "Invalid zip file, did not contain a ./root directory." >&2 + exit 1 +fi + +echo "Changing current symlink" +rm -f current +ln -s versions/$resolved_version/root current + +if [ -d current/bin ] +then + ( + cd $name/$instance/current + find bin -type f | xargs chmod +x + ) +fi + +( + cd versions/$resolved_version + if [ -d scripts ] + then + find scripts | xargs chmod +x + fi + + if [ -x scripts/postinstall ] + then + echo "Running postinstall..." + cd root + set +e + env -i \ + PATH=/bin:/usr/bin \ + APPSH_APPS=$apps \ + APPSH_HOME=$APPSH_HOME \ + APPSH_VERSION=$resolved_version \ + ../scripts/postinstall + set -e + ret=`echo $?` + if [ "$ret" != 0 ] + then + echo "Postinstall failed!" + exit 1 + fi + echo "Postinstall completed successfully" + fi +) + +# if [ -r $apps/.app/var/list ] +# then +# sed "/^$name:$instance/d" $apps/.app/var/list > $apps/.app/var/list.new +# fi +# echo "$name:$instance:$version:$url" >> $apps/.app/var/list.new +# mv $apps/.app/var/list.new $apps/.app/var/list diff --git a/libexec/app-resolver-maven b/libexec/app-resolver-maven new file mode 100755 index 0000000..cb137fa --- /dev/null +++ b/libexec/app-resolver-maven @@ -0,0 +1,231 @@ +#!/bin/bash -e + +set -u + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +usage() { + message=${1-} + + if [ ! -z "$message" ] + then + echo $message + fi + + echo "usage: $subapp init -r <repo> <maven url>" + echo "usage: $subapp resolve-version" + echo "usage: $subapp download-version -v <version> -f <download target>" + exit 1 +} + +slash() { + echo $1 | sed "s,\.,/,g" +} + +# TODO: support file:// repositories +# TODO: look in the local repository first +get() { + local url=$1; shift + local file=$1; shift + + if [[ $url == file://* ]] + then + get_file "$url" "$file" + else + get_http "$url" "$file" + fi +} + +get_file() { + url=${1:7} + + cp "$url" "$2" +} + +get_http() { + curl -o $file $url -D curl.tmp + + exit=`grep "^HTTP/[0-9]\.[0-9] 200 .*" curl.tmp >/dev/null; echo $?` + head=`head -n 1 curl.tmp` + rm -f curl.tmp + if [ "$exit" != 0 ] + then + echo "Unable to download $url: $head" >&2 + exit 1 + fi +} + +download_artifact() { + local file="$1"; shift + local url="$1"; shift + + echo "Downloading $url.md5" + get $url.md5 $file.md5 + local expected_md5="`cat $file.md5`" + + if [ -r $file ] + then + if [ "$expected_md5" == "`calculate_md5 $file`" ] + then + echo "Artifact already downloaded." + else + rm -f "$file" + fi + return 0 + fi + echo "Downloading artifact: $url" + get $url $file + + local actual_md5="`calculate_md5 $file`" + if [ "$expected_md5" == "$actual_md5" ] + then + echo "Artifact downloaded." + else + echo "Invalid checksum. Expected $expected_md5, got $actual_md5" >&2 + exit 1 + fi +} + +resolve_version() { + local group_id=`app-conf get maven.group_id` + local artifact_id=`app-conf get maven.artifact_id` + local version=`app-conf get maven.version` + + repo=`app-conf get maven.repo` + + if [[ ! $version == *-SNAPSHOT ]] + then + app-conf set app.version "$version" + exit 0 + fi + + echo "Resolving version $version..." + resolve_snapshot $group_id $artifact_id $version +} + +resolve_snapshot() { + local group_id=$1; shift + local artifact_id=$1; shift + local version=$1; shift + + repo=`app-conf get maven.repo` + + local group_id_slash=`slash $group_id` + + local base_path=$group_id_slash/$artifact_id/$version + + mkdir -p .app/cache/$base_path + + local l=.app/cache/$base_path/maven-metadata.xml + local r=$repo/$base_path/maven-metadata.xml + + get $r $l +# x=`xmlstarlet sel -t -m '//snapshotVersion[extension[text()="zip"]]' -v value $l` + set -- `xmlstarlet sel -t -m '/metadata/versioning/snapshot' -v "timestamp|buildNumber" $l` + snapshot_version="$1-$2" + + if [[ $snapshot_version == "" ]] + then + echo "Unable to resolve SNAPSHOT version for $group_id:$artifact_id:$version" + exit 1 + fi + + app-conf set maven.snapshotVersion "$snapshot_version" + app-conf set app.version "${version%-SNAPSHOT}-$snapshot_version" +} + +download_version() { + resolved_version="" + target="" + while getopts "v:f:" opt + do + case $opt in + v) + resolved_version="$OPTARG" + ;; + f) + target="$OPTARG" + ;; + *) + usage "Invalid option: $OPTARG" + ;; + esac + done + + if [[ $resolved_version == "" || $target == "" ]] + then + usage + fi + + repo=`app-conf get maven.repo` + group_id=`app-conf get maven.group_id` + artifact_id=`app-conf get maven.artifact_id` + version=`app-conf get maven.version` + + group_id_slash=`slash $group_id` + base_path=$group_id_slash/$artifact_id/$version + + mkdir -p .app/cache/$base_path + + l=.app/cache/$base_path/$artifact_id-$resolved_version.zip + r=$repo/$base_path/$artifact_id-$resolved_version.zip + + echo "Downloading $group_id:$artifact_id:$resolved_version..." + get $r $l + + ln -s "`pwd`/$l" "$target" +} + +init() { + while getopts "r:" opt + do + case $opt in + r) + app-conf set maven.repo "$OPTARG" + shift 2 + OPTIND=1 + ;; + *) + usage "Invalid option: $OPTARG" + ;; + esac + done + + x=`echo $1 | tr ":" " "`; set -- $x + group_id=$1 + artifact_id=$2 + version=$3 + + if [ -z "$group_id" -o -z "$artifact_id" -o -z "$version" ] + then + usage "Invalid Maven coordinates: $coordinates" + fi + + app-conf set maven.group_id "$group_id" + app-conf set maven.artifact_id "$artifact_id" + app-conf set maven.version "$version" +} + +subapp="$0"; +command="$1"; shift + +case "$command" in + init) + init "$@" + ;; + resolve-version) + resolve_version + ;; + download-version) + download_version "$@" + ;; + *) + usage + ;; +esac + +exit 0 diff --git a/test/app-cat-conf.bats b/test/app-cat-conf.bats new file mode 100755 index 0000000..a95049a --- /dev/null +++ b/test/app-cat-conf.bats @@ -0,0 +1,56 @@ +#!/usr/bin/env bats +# vim: set filetype=sh: + +load utils + +setup_inner() { + export APPSH_DEFAULT_CONFIG=/dev/null +} + +@test "app-cat-conf" { + app_libexec app-cat-conf -f $APPSH_HOME/test/data/app-cat-conf/config-1 + echo_lines + eq '${lines[0]}' "baz.kiz=zap" + eq '${lines[1]}' "baz.wat=baz" + eq '${lines[2]}' "foo.bar=wat" + eq '${lines[3]}' "foo.baz=kaz" + eq '${lines[4]}' "foo.wat=foo" + eq '${#lines[*]}' 5 +} + +@test "app-cat-conf -g baz" { + app_libexec app-cat-conf -f $APPSH_HOME/test/data/app-cat-conf/config-1 -n "baz\..*" + echo_lines + eq '${lines[0]}' "baz.kiz=zap" + eq '${lines[1]}' "baz.wat=baz" + eq '${#lines[*]}' 2 +} + +@test "app-cat-conf -k wat" { + app_libexec app-cat-conf -f $APPSH_HOME/test/data/app-cat-conf/config-1 -n ".*\.wat" + echo_lines + eq '${lines[0]}' "baz.wat=baz" + eq '${lines[1]}' "foo.wat=foo" + eq '${#lines[*]}' 2 +} + +@test "app-cat-conf -g baz -k wat" { + app_libexec app-cat-conf -f $APPSH_HOME/test/data/app-cat-conf/config-1 -n "baz\.wat" + echo_lines + eq '${lines[0]}' "baz.wat=baz" + eq '${#lines[*]}' 1 +} + +@test "uses \$APPSH_DEFAULT_CONFIG" { + APPSH_DEFAULT_CONFIG=$APPSH_HOME/test/data/app-cat-conf/config-2 + app_libexec app-cat-conf -f /dev/null + echo_lines + eq '${lines[0]}' "foo.bar=wat" + eq '${#lines[*]}' 1 + + app_libexec app-cat-conf -f $APPSH_HOME/test/data/app-cat-conf/config-3 + echo_lines + eq '${lines[0]}' "foo.bar=baz" + eq '${lines[1]}' "foo.wat=bar" + eq '${#lines[*]}' 2 +} diff --git a/test/app-common.bats b/test/app-common.bats new file mode 100755 index 0000000..d2173f5 --- /dev/null +++ b/test/app-common.bats @@ -0,0 +1,14 @@ +#!/usr/bin/env bats +# vim: set filetype=sh: + +load utils + +@test "grep_path" { + . $APPSH_HOME/lib/common + + x=`grep_path "app-.*" "$APPSH_HOME/test/data/app-common:/does-not-exist"|sort|sed s,$APPSH_HOME/,,|xargs` + [[ $x == "test/data/app-common/app-bar test/data/app-common/app-faz test/data/app-common/app-foo" ]] + + x=`grep_path "app-f.*" "$APPSH_HOME/test/data/app-common:/does-not-exist"|sort|sed s,$APPSH_HOME/,,|xargs` + [[ $x == "test/data/app-common/app-faz test/data/app-common/app-foo" ]] +} diff --git a/test/app-conf.bats b/test/app-conf.bats index 10e2757..08676d2 100755 --- a/test/app-conf.bats +++ b/test/app-conf.bats @@ -3,92 +3,117 @@ load utils -@test "./app conf - happy day" { - i=env-a - n=app-a - - mkzip "app-a" - app instance install -r file -u $BATS_TEST_DIRNAME/data/app-a.zip -n $n -i $i -v 1.0 - [ $status -eq 0 ] - - app -n $n -i $i conf; echo_lines - [ $status -eq 0 ] - [ "$output" = "app.bin bin/app-a " ] +setup_inner() { + mkdir .app; touch .app/config + export APPSH_DEFAULT_CONFIG=/dev/null +} - app -n $n -i $i conf set group.foo bar; echo_lines - [ $status -eq 0 ] +@test "./app conf - happy day" { + app conf; echo_lines + echo "app.bin=bin/app-a" > .app/config + eq '$status' 0 + eq '${#lines[*]}' 0 + + app conf set g.foo bar; echo_lines + eq '$status' 0 + + app conf; echo_lines + eq '$status' 0 + eq '${lines[0]}' "app.bin bin/app-a " + eq '${lines[1]}' "g.foo bar " + eq '${#lines[*]}' 2 + + app conf get g.foo; echo_lines + eq '$status' 0 + eq '${lines[0]}' "bar" + eq '${#lines[*]}' 1 + + app conf delete g.foo; echo_lines + eq '$status' 0 + + app conf; echo_lines + eq '$status' 0 + eq '${lines[0]}' "app.bin bin/app-a " + eq '${#lines[*]}' 1 +} - app -n $n -i $i conf; echo_lines - [ $status -eq 0 ] - [ "$output" = "app.bin bin/app-a -group.foo bar " ] +@test "./app conf - defaults to 'list'" { + echo "app.bin=bin/app-a" > .app/config - app -n $n -i $i conf delete group.foo; echo_lines - [ $status -eq 0 ] + app conf; echo_lines + eq '$status' 0 + eq '${#lines[*]}' 1 + eq '${lines[0]}' "app.bin bin/app-a " +} - app -n $n -i $i conf; echo_lines - [ $status -eq 0 ] - [ "$output" = "app.bin bin/app-a " ] +@test "./app conf wat" { + app conf wat; echo_lines + eq '$status' 1 + eq '${lines[0]}' "Error: Unknown command: wat" } @test "./app conf list" { - i=env-a - n=app-a - - mkzip "app-a" - app instance install -r file -u $BATS_TEST_DIRNAME/data/app-a.zip -n $n -i $i -v 1.0 - [ $status -eq 0 ] + echo "app.bin=bin/app-a" > .app/config - app -n $n -i $i conf; echo_lines - [ $status -eq 0 ] - [ "$output" = "app.bin bin/app-a " ] + app conf; echo_lines + eq '$status' 0 + eq '${#lines[*]}' 1 + eq '${lines[0]}' "app.bin bin/app-a " - app -n $n -i $i conf list; echo_lines - [ $status -eq 0 ] - [ "$output" = "app.bin bin/app-a " ] + app conf list; echo_lines + eq '$status' 0 + eq '${#lines[*]}' 1 + eq '${lines[0]}' "app.bin bin/app-a " - app -n $n -i $i conf list foo; echo_lines - [ $status -eq 1 ] + app conf list foo; echo_lines + eq '$status' 1 } -@test "./app conf list-group" { - i=env-a - n=app-a - - mkzip "app-a" - app instance install -r file -u $BATS_TEST_DIRNAME/data/app-a.zip -n $n -i $i -v 1.0 - [ $status -eq 0 ] - - app -n $n -i $i conf set mygroup.a 1 - [ $status -eq 0 ] - app -n $n -i $i conf set mygroup.b 1 - [ $status -eq 0 ] - app -n $n -i $i conf set mygroup.c 2 - [ $status -eq 0 ] - app -n $n -i $i conf set othergroup.a 1 - [ $status -eq 0 ] - - app -n $n -i $i conf list-group mygroup; echo_lines - [ $status -eq 0 ] - [ "$output" = "a 1 -b 1 -c 2 " ] -} +#@test "./app conf list-group" { +# app conf set mygroup a 1 +# eq '$status' 0 +# app conf set mygroup b 1 +# eq '$status' 0 +# app conf set mygroup c 2 +# eq '$status' 0 +# app conf set othergroup a 1 +# eq '$status' 0 +# +# app conf list; echo_lines +# eq '$status' 0 +# app conf list-group mygroup; echo_lines +# eq '$status' 0 +# eq '${lines[0]}' "mygroup.a 1 " +# eq '${lines[1]}' "mygroup.b 1 " +# eq '${lines[2]}' "mygroup.c 2 " +# eq '${#lines[*]}' 3 +#} @test "./app conf set" { - i=env-a - n=app-a + echo "app.bin=bin/app-a" > .app/config - mkzip "app-a" - app instance install -r file -u $BATS_TEST_DIRNAME/data/app-a.zip -n $n -i $i -v 1.0 - [ $status -eq 0 ] + app conf set group; echo_lines + eq '$status' 1 - app -n $n -i $i conf set group; echo_lines - [ $status -eq 1 ] + app conf set group.foo; echo_lines + eq '$status' 1 + + app conf set group.foo bar; echo_lines + eq '$status' 0 + eq '${#lines[*]}' 0 + + app conf; echo_lines + eq '$status' 0 + eq '${lines[0]}' "app.bin bin/app-a " + eq '${lines[1]}' "group.foo bar " + eq '${#lines[*]}' 2 +} - app -n $n -i $i conf set group.foo; echo_lines - [ $status -eq 1 ] +@test "./app conf list - with duplicate entries" { + echo "foo.bar=awesome" > .app/config + echo "foo.bar=awesome" >> .app/config - app -n $n -i $i conf set group.foo bar; echo_lines - [ $status -eq 0 ] + app conf list; echo_lines + eq '$status' 0 + eq '${lines[0]}' "foo.bar awesome " } diff --git a/test/app-init.bats b/test/app-init.bats new file mode 100755 index 0000000..b62c42e --- /dev/null +++ b/test/app-init.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bats +# vim: set filetype=sh: + +load utils + +#@test "Invalid resolver" { +# app init -d my-app wat; echo_lines +# eq '$status' 1 +# eq '${#lines[*]}' 1 +# eq '${lines[0]}' "No such resolver: wat" +#} + +#@test "Already installed" { +# mkdir -p my-app/.apps +# app init -d my-app maven; echo_lines +# eq '$status' 1 +# eq '${#lines[*]}' 1 +# match '${lines[0]}' "my-app" +#} + +@test "Happy day" { + mkzip app-a + + REPO=$BATS_TMPDIR/repo + + if [ ! -f $REPO/org/example/app-a/1.0-SNAPSHOT/maven-metadata.xml ] + then + mvn deploy:deploy-file -Durl=file://$REPO \ + -Dfile=`echo $APPSH_HOME/test/data/app-a.zip` -DgeneratePom \ + -DgroupId=org.example -DartifactId=app-a -Dversion=1.0-SNAPSHOT -Dpackaging=zip + fi + + app init -d my-app maven -r "file://$BATS_TMPDIR/repo" org.example:app-a:1.0-SNAPSHOT; echo_lines + eq '$status' 0 + eq '${lines[0]}' "Resolving version 1.0-SNAPSHOT..." + match '${lines[1]}' "Resolved version to 1.0-.*" + match '${lines[2]}' "Downloading org.example:app-a:1.0-.*" + + eq '${#lines[*]}' 3 + + is_directory "my-app/.app" +} diff --git a/test/app-install.bats b/test/app-install.bats index 38304be..18a84bc 100755 --- a/test/app-install.bats +++ b/test/app-install.bats @@ -5,12 +5,11 @@ load utils # TODO: Add test for installing duplicate version -@test "./app instance install app-a" { +@test "./app install app-a" { mkzip "app-a" - app instance install \ + app install \ -r file \ - -u $BATS_TEST_DIRNAME/data/app-a.zip \ - -n app-a -i prod + -u $BATS_TEST_DIRNAME/data/app-a.zip echo_lines [ $status -eq 0 ] @@ -29,7 +28,7 @@ Postinstall completed successfully" ] app instance install \ -r file \ -u $BATS_TEST_DIRNAME/data/install-test-env.zip \ - -n install-test-env -i prod -v 1.0 + -v 1.0 echo_lines [ $status -eq 0 ] [ "$output" = "Creating instance 'prod' for 'install-test-env' diff --git a/test/data/app-cat-conf/config-1 b/test/data/app-cat-conf/config-1 new file mode 100644 index 0000000..9d78d45 --- /dev/null +++ b/test/data/app-cat-conf/config-1 @@ -0,0 +1,7 @@ + foo.bar = wat +foo.bar=wat +foo.baz=kaz +foo.wat=foo + +baz.wat=baz +baz.kiz=zap diff --git a/test/data/app-cat-conf/config-2 b/test/data/app-cat-conf/config-2 new file mode 100644 index 0000000..288815d --- /dev/null +++ b/test/data/app-cat-conf/config-2 @@ -0,0 +1 @@ +foo.bar=wat diff --git a/test/data/app-cat-conf/config-3 b/test/data/app-cat-conf/config-3 new file mode 100644 index 0000000..569ea27 --- /dev/null +++ b/test/data/app-cat-conf/config-3 @@ -0,0 +1,4 @@ +# same key as config-2, different value +foo.bar=baz + +foo.wat=bar diff --git a/test/data/app-common/app-bar b/test/data/app-common/app-bar new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/test/data/app-common/app-bar diff --git a/test/data/app-common/app-faz b/test/data/app-common/app-faz new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/test/data/app-common/app-faz diff --git a/test/data/app-common/app-foo b/test/data/app-common/app-foo new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/test/data/app-common/app-foo diff --git a/test/utils.bash b/test/utils.bash index cd21966..fa2602e 100644 --- a/test/utils.bash +++ b/test/utils.bash @@ -6,21 +6,25 @@ workdir=test-run exit_usage=1 exit_usage_wrong=0 -echo_lines() { - for line in "${lines[@]}"; do echo $line; done - echo status=$status -} - -APPSH=$(pwd)/app - setup() { - APPSH_APPS=$BATS_TMPDIR/app.sh - APPSH_HOME=$(cd $BATS_TEST_DIRNAME/../..; echo `pwd`/app.sh) - APPSH_APPS_CANONICAL=$(cd -P $APPSH_APPS; pwd) + PATH=/bin:/usr/bin + PATH=$PATH:$APPSH_HOME + APPSH_HOME=$(cd $BATS_TEST_DIRNAME/..; echo `pwd`) + rm -rf $BATS_TMPDIR/app.sh mkdir $BATS_TMPDIR/app.sh cd $BATS_TMPDIR/app.sh - ln -s $APPSH + + if [ "`declare -f setup_inner >/dev/null; echo $?`" = 0 ] + then + setup_inner + fi +} + +echo_lines() { + echo lines: + for line in "${lines[@]}"; do echo $line; done + echo status=$status } mkzip() { @@ -32,8 +36,16 @@ mkzip() { } app() { - echo ./app $@ - run ./app $@ + echo app $@ + run $APPSH_HOME/app $@ +} + +app_libexec() { + local x=`PATH=$APPSH_HOME/libexec:/bin:/usr/bin which $1` + + echo libexec/$@ + shift + run "$x" $@ } describe() { @@ -59,3 +71,43 @@ can_not_read() { return 1 fi } + +is_directory() { + if [ ! -d "$1" ] + then + echo "Not a directory: $1" 2>&1 + return 1 + fi +} + +eq() { + local ex="$1" + local e="$2" + local a="`eval echo $ex`" + + if [[ $e == $a ]] + then + return 0 + fi + + echo "Assertion failed: $ex" + echo "Expected: $e" + echo "Actual: $a" + exit 1 +} + +match() { + local ex="$1" + local regex="$2" + local a="`eval echo $ex`" + + if [[ $a =~ $regex ]] + then + return 0 + fi + + echo "Assertion failed: $ex =~ $a" + echo "Expected: $e" + echo "Actual: $a" + exit 1 +} |