diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/app-conf | 135 | ||||
-rwxr-xr-x | bin/app-init | 79 | ||||
-rwxr-xr-x | bin/app-instance | 502 | ||||
-rwxr-xr-x | bin/app-operate | 73 | ||||
-rwxr-xr-x | bin/pid-method | 113 |
5 files changed, 902 insertions, 0 deletions
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/bin/app-instance b/bin/app-instance new file mode 100755 index 0000000..02e3c0f --- /dev/null +++ b/bin/app-instance @@ -0,0 +1,502 @@ +#!/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" +else + repo="http://repo1.maven.org" +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 +} + +method_install_usage() { + if [ -n "$1" ] + then + echo "Error:" "$@" >&2 + fi + + echo "usage: install <-r resolver> -u <url>" >&2 + echo "" >&2 + echo "Install package from a Maven repository:" >&2 + echo " $0 [-n name] [-i instance] instance install -r maven -u groupId:artifactId:version" >&2 + echo "" >&2 + echo "Install package from a file:" >&2 + echo " $0 [-n name] [-i instance] instance install -r file -u file [-v version]" >&2 + echo "The version defaults to the current timestamp" >&2 + exit 1 +} + +method_install() { + local name="$1"; shift + local instance="$1"; shift + local version + local resolver + local url + local groupId + local artifactId + local zip_file + + + if [ $# -eq 0 ] + then + method_install_usage + fi + + while getopts "n:i:v:r:u:" opt + do + case $opt in + n) + name=$OPTARG + ;; + i) + instance=$OPTARG + ;; + v) + version=$OPTARG + ;; + r) + resolver=$OPTARG + ;; + u) + url=$OPTARG + ;; + \?) + method_install_usage "Invalid option: -$OPTARG" + ;; + esac + done + + if [ -z "$name" ] + then + method_install_usage "Missing required argument: -i name." + fi + + if [ -z "$instance" ] + then + method_install_usage "Missing required argument: -i instance." + fi + + if [ -z "$resolver" ] + then + method_install_usage "Missing required option: -r resolver" + fi + + if [ -z "$url" ] + then + method_install_usage "Missing required option: -u url" + fi + + case "$resolver" in + maven) + url=`echo $url | tr ":" " "`; set -- $url + groupId=$1 + artifactId=$2 + version=$3 + + if [ -z "$groupId" -o -z "$artifactId" -o -z "$version" ] + then + method_install_usage "Invalid Maven url." + fi + + local groupIdSlash=$(echo $groupId | sed "s,\.,/,g") + if [ "`echo $version | sed -n s,.*-SNAPSHOT$,SNAPSHOT,p`" == "SNAPSHOT" ] + then + echo "Resolving version $version..." + local resolved_version=`resolve_snapshot $groupId $groupIdSlash $artifactId $version` + if [ -z "$resolved_version" ] + then + echo "Unable to resolve version." + exit 1 + fi + echo "Resolved version $version to $resolved_version" + else + resolved_version=$version + fi + + zip_file=$apps/.app/var/download/$groupId-$artifactId-$resolved_version.zip + artifact_url=$repo/$groupIdSlash/$artifactId/$version/$artifactId-$resolved_version.zip + + download_artifact "$zip_file" "$artifact_url" + ;; + 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 $name/$instance/versions/$resolved_version ] + then + echo "Version $resolved_version is already installed" + exit 1 + fi + + if [ ! -d $name/$instance ] + then + echo "Creating instance '$instance' for '$name'" + mkdir -p $name/$instance + fi + + mkdir -p $name/$instance/versions/$resolved_version + + echo "Unpacking..." + unzip -q -d $name/$instance/versions/$resolved_version $zip_file + + if [ ! -d $name/$instance/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 $apps/$name/$instance/current + ln -s versions/$resolved_version/root $apps/$name/$instance/current + + if [ -d $name/$instance/current/bin ] + then + ( + cd $name/$instance/current + find bin -type f | xargs chmod +x + ) + fi + + ( + cd $name/$instance/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_NAME=$name \ + APPSH_INSTANCE=$instance \ + 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 +} + +method_set_current_usage() { + if [ -n "$1" ] + then + echo "Error:" "$@" >&2 + fi + + echo "usage: set-current -v version" >&2 + exit 1 +} + +method_set_current() { + local name="$1"; shift + local instance="$1"; shift + local version + + if [ $# -eq 0 ] + then + method_set_current_usage + fi + + while getopts "n:i:v:" opt + do + case $opt in + n) + name=$OPTARG + ;; + i) + instance=$OPTARG + ;; + v) + version=$OPTARG + ;; + \?) + method_set_current_usage "Invalid option: -$OPTARG" + ;; + esac + done + + if [ -z "$version" ] + then + echo "Missing required option -v version." >&2 + exit 1 + fi + + assert_is_instance method_set_current_usage "$name" "$instance" "no" + + if [ ! -d $apps/$name/$instance/versions/$version ] + then + echo "Invalid version: $version." + exit 1 + fi + + rm -f $apps/$name/$instance/current + ln -s versions/$version/root $apps/$name/$instance/current + + return 0 +} + +method_list_usage() { + if [ -n "$1" ] + then + echo "Error:" "$@" >&2 + fi + + echo "usage: list [-n name] [-P field]" >&2 + echo "" + echo "List all installed applications:" >&2 + echo " list" >&2 + echo "" + echo "List all applications with the selected fields with parseable output:" >&2 + echo " list -P instance -P version -n foo" >&2 + exit 1 +} + +method_list() { + local filter_name="$1"; shift + local filter_instance="$1"; shift + local mode="pretty" + local vars + local filter_name + + while getopts "P:n:i:" opt + do + case $opt in + P) + mode="parseable" + vars="$vars $OPTARG" + ;; + n) + filter_name=$OPTARG + ;; + i) + filter_instance=$OPTARG + ;; + \?) + method_list_usage "Invalid option: -$OPTARG" + ;; + esac + done + + if [ ! -r $apps/.app/var/list ] + then + return + fi + + if [ $mode = "pretty" ] + then + printf "%-20s %-20s %-20s\n" "Name" "Instance" "Version" + list_apps "$filter_name" "$filter_instance" name instance version | (IFS=:; while read name instance version + do + printf "%-20s %-20s %-20s\n" "$name" "$instance" "$version" + done) + else + list_apps "$filter_name" "$filter_instance" $vars + fi +} + +method_list_versions_usage() { + if [ -n "$1" ] + then + echo "Error:" "$@" >&2 + fi + + echo "usage: list-versions -n name -i instance [-P]" >&2 + exit 1 +} + +method_list_versions() { + local filter_name="$1"; shift + local instance="$1"; shift + local version + local mode="pretty" + + if [ $# -eq 0 ] + then + method_list_versions_usage + fi + + while getopts "n:i:P" opt + do + case $opt in + n) + name=$OPTARG + ;; + i) + instance=$OPTARG + ;; + v) + version=$OPTARG + ;; + P) + mode="parseable" + ;; + \?) + method_list_versions_usage "Invalid option: -$OPTARG" + ;; + esac + done + + assert_is_instance method_list_versions_usage "$name" "$instance" "no" + + if [ $mode = "pretty" ] + then + echo "Available versions for $name/$instance:" + fi + + find_versions $name $instance + + return 0 +} + +method_instance_usage() { + if [ -n "$1" ] + then + echo "Error:" $@ >&2 + fi + + echo "usage: $0 instance <method>" >&2 + echo "" >&2 + echo "Available methods:" >&2 + echo " install - Installs an application" >&2 + echo " list - List all installed applications" >&2 + echo " list-versions - List all available versions for a single application" >&2 + echo " set-current - Set the current version" >&2 +} + +method_instance() { + local name="$1"; shift + local instance="$1"; shift + local method="$1" + + if [ $# -gt 0 ] + then + shift + fi + + case "$method" in + install) method_install "$name" "$instance" "$@" ;; + list) method_list "$name" "$instance" "$@" ;; + list-versions) method_list_versions "$name" "$instance" "$@" ;; + set-current) method_set_current "$name" "$instance" "$@" ;; + *) + if [ -z "$method" ] + then + method_instance_usage + else + method_instance_usage "Unknown method $method" + fi + ;; + esac + exit $? +} diff --git a/bin/app-operate b/bin/app-operate new file mode 100755 index 0000000..dc16780 --- /dev/null +++ b/bin/app-operate @@ -0,0 +1,73 @@ +#!/bin/bash + +if [[ $APPSH_HOME == "" ]] +then + APPSH_HOME=`dirname "$0"` + APPSH_HOME=`cd "$APPSH_HOME/.." && pwd` +fi + +operate_usage() { + if [ -n "$1" ] + then + echo "Error:" "$@" >&2 + fi + + echo "usage: $0 [operate method] -n name -i instance" >&2 + exit 1 +} + +method_operate_usage() { + if [ -n "$1" ] + then + echo "Error:" $@ >&2 + fi + + echo "usage: $0 operate <operate method>" >&2 + echo "" >&2 + echo "Available operate methods:" >&2 + echo " start" >&2 + echo " stop" >&2 + echo " restart" >&2 + echo " status" >&2 +} + +method_operate() { + local name="$1"; shift + local instance="$1"; shift + local method="$1" + + if [ $# -gt 0 ] + then + shift + fi + + 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 + bin=$APPSH_HOME/.app/lib/pid-method + fi + + if [ ! -x "$name/$instance/current/$bin" ] + then + echo "Invalid executable: $bin" >&2 + exit 1 + fi + + case "$method" in + start) run_app "$name" "$instance" "$bin" "start" "$@" ;; + stop) run_app "$name" "$instance" "$bin" "stop" "$@" ;; + status) run_app "$name" "$instance" "$bin" "status" "$@" ;; + restart) run_app "$name" "$instance" "$bin" "restart" "$@" ;; + run) run_app "$name" "$instance" "$bin" "run" "$@" ;; + *) + if [ -z "$method" ] + then + method_operate_usage + else + method_operate_usage "Unknown method $method" + fi + ;; + esac + exit $? +} diff --git a/bin/pid-method b/bin/pid-method new file mode 100755 index 0000000..29f6b4f --- /dev/null +++ b/bin/pid-method @@ -0,0 +1,113 @@ +#!/bin/bash -e + +set -u + +. $APPSH_HOME/.app/lib/app-conf + +pid_file=$APPSH_APPS/.app/var/pid/$APPSH_NAME-$APPSH_INSTANCE.pid +bin=`get_conf $APPSH_APPS $APPSH_NAME $APPSH_INSTANCE app.bin` + +cd $APPSH_APPS/$APPSH_NAME/$APPSH_INSTANCE/current + +if [ -z "$bin" ] +then + echo "Missing required configuration: app.bin." >&2 + exit 1 +fi + +if [ ! -r "$bin" ] +then + echo "No such file: $bin" >&2 + exit 1 +fi + +chmod +x "$bin" + +PID= +if [ -r $pid_file ] +then + PID="`cat $pid_file`" +fi + +do_status() { + if [ -z "$PID" ] + then + echo stopped + else + if [ `ps -p "$PID" 2>/dev/null | wc -l` -gt 1 ] + then + echo running + else + echo crashed + fi + fi +} + +method_start() { + case `do_status` in + running) + echo "The application is already running as $PID." + exit 1 + ;; + esac + + $bin <&- 1<&- 2<&- & + + PID=$! + echo "Application launched as $PID" + echo $PID > $pid_file + + return 0 +} + +method_stop() { + case `do_status` in + stopped) + echo "The application not running." + exit 1 + ;; + crashed) + echo "The application crashed. Was running as $PID" + # TODO: should this remove the PID file? + # That makes it possible to run "stop" to stop "status" from showing "crashed" + exit 1 + ;; + esac + + signal="-9" + echo -n "Sending kill $signal to $PID, waiting for shutdown" + kill $signal $PID + + while [ "`do_status`" == "running" ] + do + sleep 1 + echo -n "." + done + + echo " OK" + rm -f $pid_file + return 0 +} + +method_status() { + case `do_status` in + running) + echo "$APPSH_NAME/$APPSH_INSTANCE is running as $PID" + ;; + stopped) + echo "$APPSH_NAME/$APPSH_INSTANCE is not running" + ;; + crashed) + echo "$APPSH_NAME/$APPSH_INSTANCE crashed. Was running as $PID" + ;; + esac +} + +case "$APPSH_METHOD" in + start) method_start ;; + stop) method_stop ;; + status) method_status ;; + *) exit 1 ;; +esac + +exit $? |