diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 41 | ||||
-rwxr-xr-x | appstore | 58 | ||||
-rw-r--r-- | appstore.config | 0 | ||||
-rwxr-xr-x | bin/appstore-init | 57 | ||||
-rwxr-xr-x | hooks/common | 3 | ||||
-rwxr-xr-x | hooks/post-receive | 55 | ||||
-rwxr-xr-x | lib/common | 64 | ||||
-rw-r--r--[-rwxr-xr-x] | lib/header (renamed from hooks/post-commit) | 4 | ||||
-rw-r--r-- | lib/header-hook | 8 | ||||
-rwxr-xr-x | libexec/appstore-init-server | 50 | ||||
-rw-r--r-- | template/README.md | 1 | ||||
-rw-r--r-- | template/apps.csv | 1 | ||||
-rw-r--r-- | test/data/my-webapp/root/app.js | 37 | ||||
-rw-r--r-- | test/it.bats | 28 | ||||
-rw-r--r-- | test/utils.bash | 176 |
16 files changed, 578 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4c4ffc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..49538be --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +all: test docs + +BINS=$(wildcard bin/*) $(wildcard libexec/*) + +BATS=$(sort $(patsubst test/%,%,$(filter-out test/X-%,$(wildcard test/*.bats)))) +TESTS=$(addprefix test-,$(BATS)) + +test-%: + @echo === $@ + @bats $(patsubst test-%,test/%,$@) + +show-tests: + @echo BATS=$(BATS) + @echo TESTS=$(TESTS) + @echo $(addprefix set_header-,$(BINS)) + +test: $(TESTS) +.PHONY: test + +docs: + @make -C docs +.PHONY: docs + +define set_header +set_header-$(1): + @count=`wc -l $(2)|cut -f 1 -d ' '`; \ + cat $(2) > x; \ + echo "# HEADER END" >> x; \ + sed '1,/HEADER END/d' $(1) >> x; \ + if [ `md5sum $(1)|cut -f 1 -d ' '` != `md5sum x|cut -f 1 -d ' '` ]; then echo Updated: $(1); cp x $(1); fi; \ + rm x +endef + $(wildcard hooks/*) + +$(foreach f,$(BINS),$(eval $(call set_header,$(f),lib/header))) +set-headers: set-hook-headers $(addprefix set_header-,$(BINS)) + +$(foreach f,$(wildcard hooks/*),$(eval $(call set_header,$(f),lib/hook-header))) +set-headers: $(addprefix set_header-,$(BINS)) + +.PHONY: set-headers diff --git a/appstore b/appstore new file mode 100755 index 0000000..eee4774 --- /dev/null +++ b/appstore @@ -0,0 +1,58 @@ +#!/bin/bash -e + +PRG="$0" +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi +done + +APPSTORE_HOME=`dirname "$PRG"` +APPSTORE_HOME=`cd "$APPSTORE_HOME" && pwd` + +. $APPSTORE_HOME/lib/common + +usage_text() { + echo "usage: $usage_app <command>" + echo "" + echo "Available porcelain commands:" + grep_path "/appstore-.*$" "$APPSTORE_HOME/bin" | \ + sed "s,^.*/appstore-, ," | \ + sort -n + echo "" + echo "Available plumbing commands:" + grep_path "/appstore-.*$" "$APPSTORE_HOME/libexec" | \ + sed "s,^.*/appstore-, ," | \ + sort -n +} + +if [ $# -eq 0 ] +then + usage +fi + +command=$1; shift + +bin=`grep_path "/appstore-$command$" "$APPSTORE_HOME/bin"` + +if [ ! -x "$bin" ] +then + bin=`grep_path "/appstore-$command$" "$APPSTORE_HOME/libexec"` + if [ ! -x "$bin" ] + then + echo "Unknown command: $command" 2>&1 + exit 1 + fi +fi + +PATH=$APPSTORE_HOME/bin:$PATH + +# TODO: this is probably a good place to clean up the environment +exec env \ + "APPSTORE_HOME=$APPSTORE_HOME" \ + "echo_debug=$echo_debug" \ + "$bin" "$@" diff --git a/appstore.config b/appstore.config deleted file mode 100644 index e69de29..0000000 --- a/appstore.config +++ /dev/null diff --git a/bin/appstore-init b/bin/appstore-init new file mode 100755 index 0000000..cbbf0f7 --- /dev/null +++ b/bin/appstore-init @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e +set -u + +APPSTORE_HOME=$(cd $(dirname "$0")/.. && pwd) + +. $APPSTORE_HOME/lib/common +# HEADER END + +usage() { + echo "usage [server] [name]" + exit 1 +} + +if [ $# -ne 3 ] +then + usage +fi + +server=$1; shift +repos=$1; shift +name=$1; shift + +if [ -e "$name" ] +then + echo "$name already exist!" + exit 1 +fi + +tmpfile=tmpfile$$ +conffile=conffile$$ +echo "Creating remote appstore..." +set +e +ssh "$server" "$APPSTORE_HOME/libexec/appstore-init-server" "$repos" "$name" > "$tmpfile" 2>&1 +ret=$? +set -e + +sed -n "s,^config: \(.*\),\1,p" $tmpfile > $conffile +repo_path=`app cat-conf -f "$conffile" -n repo.path | cut -f 2- -d =` +repo_ok=`app cat-conf -f "$conffile" -n repo.ok | cut -f 2- -d =` + +if [ "$ret" != 0 ] +then + echo "Initialization failed. Server output:" + cat $tmpfile + exit 1 +fi + +echo "Cloning repository..." +git clone -q "$server:$repo_path" "$name" +cd $name +git remote rename origin cloud +cd .. + +echo "$name is open for cloud business!" +rm *$$ diff --git a/hooks/common b/hooks/common deleted file mode 100755 index 7a693aa..0000000 --- a/hooks/common +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - - diff --git a/hooks/post-receive b/hooks/post-receive new file mode 100755 index 0000000..ced1fc4 --- /dev/null +++ b/hooks/post-receive @@ -0,0 +1,55 @@ +#!/bin/bash + +set -e +set -u + +APPSTORE_HOME=$(cd $(dirname "$0")/.. && pwd) + +. $APPSTORE_HOME/lib/common +# HEADER END + +# stdin contains the refs pushed. + +APPS="$(git config appstore.apps)" + +REPO="$(pwd)" + +cd "$APPS" + +unset GIT_DIR +git pull -q "$REPO" master + +IFS=$'\t' +csvtool -u TAB namedcol dir,resolver,resolver_args,version,state apps.csv | \ +while read dir resolver resolver_args version state +do + if [ -d "$dir" ] + then + cd $dir + old_version=$(app conf get app.version) + + if [[ $new_version == $old_version ]] + then + continue + fi + + echo "Updating $dir to $version" + app conf set app.version "$version" + app update + cd .. + else + echo "New application: $dir" + app init -d "$dir" "$resolver" "$resolver_args" + fi + + cd "$dir" + if [[ $state == enabled ]] + then + echo "Starting appliation" + app start + else + echo "Stopping appliation" + app stop + fi +done +unset IFS diff --git a/lib/common b/lib/common new file mode 100755 index 0000000..473c8b1 --- /dev/null +++ b/lib/common @@ -0,0 +1,64 @@ +#!/bin/bash + +# Sanity check to make sure that app.sh is installed +APPSH_HOME="`cd $APPSTORE_HOME/../app.sh; pwd`" + +if [ ! -d "$APPSH_HOME" ] +then + echo "app.sh has to be installed at $APPSH_HOME" + exit 1 +fi + +PATH=$APPSH_HOME:$PATH + +show_help() { + message=${1-} + + if [[ $message != "" ]] + then + echo $message + fi + + if [ "`declare -f usage_text >/dev/null; echo $?`" = 0 ] + then + usage_text + else + echo "The command $usage_app does not have any usage info." + fi + exit 1 +} + +usage() { + message=${1-} + + if [[ $message != "" ]] + then + echo $message >&2 + fi + + if [ "`declare -f usage_text >/dev/null; echo $?`" = 0 ] + then + usage_text >&2 + fi + exit 1 +} + +debug() { + [[ $echo_debug == no ]] || echo "D: $usage_app: $@" 2>&1 +} + +info() { + echo "I: $usage_app: $@" 2>&1 +} + +fatal() { + echo "$usage_app: fatal: $@" 2>&1 + exit 1 +} + +grep_path() { + local regex="$1"; shift + local path="$1"; shift + + find `echo $path | tr : " "` -type f -executable 2>/dev/null | (egrep "$regex" || exit 0) +} diff --git a/hooks/post-commit b/lib/header index 63508c2..ccd343e 100755..100644 --- a/hooks/post-commit +++ b/lib/header @@ -5,6 +5,4 @@ set -u APPSTORE_HOME=$(cd $(dirname "$0")/.. && pwd) -. $APPSTORE_HOME/hooks/common - -echo "post-commit!!" +. $APPSTORE_HOME/lib/common diff --git a/lib/header-hook b/lib/header-hook new file mode 100644 index 0000000..ccd343e --- /dev/null +++ b/lib/header-hook @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e +set -u + +APPSTORE_HOME=$(cd $(dirname "$0")/.. && pwd) + +. $APPSTORE_HOME/lib/common diff --git a/libexec/appstore-init-server b/libexec/appstore-init-server new file mode 100755 index 0000000..1417469 --- /dev/null +++ b/libexec/appstore-init-server @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e +set -u + +APPSTORE_HOME=$(cd $(dirname "$0")/.. && pwd) + +. $APPSTORE_HOME/lib/common +# HEADER END + +root=$1; shift +name=$1; shift + +echo "Creating appstore $name" + +repo="$root/repos/$name" +apps="$root/appstores/$name" + +if [ -e "$repo" ] +then + echo "$name already exist!" + exit 1 +fi + +# Create the git repository +git init -q --bare "$repo" + +# Clone the repository +git init -q "$apps" +cd "$apps" + +# Copy the template project +cp -r "$APPSTORE_HOME/template/"* . +git add -A +git config user.name "Appstore Bot" +git config user.email nobody@example.org +git commit -q -m "Created new appstore: $name." +git push -q "$repo" master +cd .. + +# Install the hooks +cd "$repo" +pwd +rm hooks/* +rmdir hooks +ln -s "$APPSTORE_HOME/hooks" hooks +git config appstore.home "$APPSTORE_HOME" +git config appstore.apps "$apps" + +echo "config: repo.path=$repo" diff --git a/template/README.md b/template/README.md new file mode 100644 index 0000000..e24090d --- /dev/null +++ b/template/README.md @@ -0,0 +1 @@ +TODO: putt something useful here diff --git a/template/apps.csv b/template/apps.csv new file mode 100644 index 0000000..3637e0e --- /dev/null +++ b/template/apps.csv @@ -0,0 +1 @@ +dir,resolver,resolver_args,version,state diff --git a/test/data/my-webapp/root/app.js b/test/data/my-webapp/root/app.js new file mode 100644 index 0000000..29298b3 --- /dev/null +++ b/test/data/my-webapp/root/app.js @@ -0,0 +1,37 @@ +var http = require("http"), + url = require("url"), + path = require("path"), + fs = require("fs") + port = process.env.PORT || 8888; + +http.createServer(function(request, response) { + + var uri = url.parse(request.url).pathname + , filename = path.join(process.cwd(), uri); + + path.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404, {"Content-Type": "text/plain"}); + response.write("404 Not Found\n"); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) filename += '/index.html'; + + fs.readFile(filename, "binary", function(err, file) { + if(err) { + response.writeHead(500, {"Content-Type": "text/plain"}); + response.write(err + "\n"); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, "binary"); + response.end(); + }); + }); +}).listen(parseInt(port, 10)); + +console.log("Static file server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown"); diff --git a/test/it.bats b/test/it.bats new file mode 100644 index 0000000..d594049 --- /dev/null +++ b/test/it.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats +# vim: set filetype=sh: + +load utils + +@test "Happy day" { + mkzip my-webapp + install_artifact "my-webapp" + install_artifact "my-webapp" 1.0 + + cd $BATS_TMPDIR + cd appstore + mkdir -p client server + cd client + + appstore init "localhost" "$BATS_TMPDIR/appstore/server" "mysetup" + cd mysetup + + # Making assertions easier. + git config user.name "Test Case" + git config user.email tester@example.org + + echo "app-1,maven,org.example:app-a:1.0-SNAPSHOT,1.0-SNAPSHOT,enabled" >> apps.csv + git commit -m "o Adding app-1." -a + git push cloud master + eq "ssh://localhost$BATS_TMPDIR/appstore/server/mysetup" `git config remote.cloud.url` + eq 1 2 +} diff --git a/test/utils.bash b/test/utils.bash new file mode 100644 index 0000000..cd6fcf0 --- /dev/null +++ b/test/utils.bash @@ -0,0 +1,176 @@ +#!/bin/bash + +workdir=test-run + +# TODO: assert that the exit code is 1 for 'usage' outputs. +exit_usage=1 +exit_usage_wrong=0 + +setup() { + ORIG_PATH=$PATH + APPSTORE_HOME=$(cd $BATS_TEST_DIRNAME/..; echo `pwd`) + PATH=/bin:/usr/bin + PATH=$PATH:$APPSTORE_HOME + + rm -rf $BATS_TMPDIR/appstore + mkdir $BATS_TMPDIR/appstore + + HOME=$BATS_TMPDIR/appstore-home + + cd $BATS_TMPDIR/appstore + + REPO=$BATS_TMPDIR/repo + REPO_URL="file://$REPO" + FIXED_REPO_URL="file://`fix_path $REPO`" + + 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() { +( + cd $BATS_TEST_DIRNAME/data/$1 + rm -f ../$1.zip + zip -qr ../$1.zip * +) +} + +install_artifact() { + local artifactId=${1}; shift + local version=${1-1.0-SNAPSHOT} + describe -Dfile=`fix_path $APPSTORE_HOME/test/data/$artifactId.zip` -DgeneratePom + PATH=$ORIG_PATH mvn deploy:deploy-file -Durl=$FIXED_REPO_URL \ + -Dfile=`fix_path $APPSTORE_HOME/test/data/$artifactId.zip` -DgeneratePom \ + -DgroupId=org.example -DartifactId=$artifactId -Dversion=$version -Dpackaging=zip +} + +check_status=yes + +app() { + echo app $@ + run $APPSTORE_HOME/app $@ + echo_lines + + if [ "$check_status" = yes ] + then + eq '$status' 0 + fi + + check_status=yes +} + +app_libexec() { + local x=`PATH=$APPSTORE_HOME/libexec:/bin:/usr/bin which $1` + + echo libexec/$@ + shift + run "$x" $@ + + echo_lines + + if [ "$check_status" = yes ] + then + eq '$status' 0 + fi + + check_status=yes +} + +fix_path_uname=`uname -s` +fix_path() { + case $fix_path_uname in + CYGWIN_NT*) + cygpath -wa $1 + ;; + *) + echo $1 + ;; + esac +} + +describe() { + echo "# " $@ >&3 +} + +can_read() { + if [ -r "$1" ] + then + return 0 + else + echo "Can't read $1" + return 1 + fi +} + +can_not_read() { + if [ ! -r "$1" ] + then + return 0 + else + echo "Can read $1" + 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 +} + +neq() { + local ex="$1" + local e="$2" + local a="`eval echo $ex`" + + if [[ $e != $a ]] + then + return 0 + fi + + echo "Not-equal 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 "Match failed: $ex =~ $regex" + echo "Value: $a" + exit 1 +} |