aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2013-10-31 21:52:24 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2013-11-01 19:58:08 +0100
commit1480338c5a3da5b6494565ab955ab67a15f19dd3 (patch)
treedf68073fad776481251093fd60a901fa7c905bec
parent40eaf2303f013321b923c5bb616f0579a01a9884 (diff)
downloadapp.sh-1480338c5a3da5b6494565ab955ab67a15f19dd3.tar.gz
app.sh-1480338c5a3da5b6494565ab955ab67a15f19dd3.tar.bz2
app.sh-1480338c5a3da5b6494565ab955ab67a15f19dd3.tar.xz
app.sh-1480338c5a3da5b6494565ab955ab67a15f19dd3.zip
app-init: Adding '-s' option to set a configuration option before the
resolver is executed and app is installed. The configuration will be available when the app's hooks are executed. o Running the documentation though aspell.
-rw-r--r--.gitignore1
-rwxr-xr-xbin/app-init25
-rw-r--r--docs/Makefile7
-rw-r--r--docs/app-cat-conf.txt19
-rw-r--r--docs/app-init.txt58
-rw-r--r--docs/app-install-file.txt2
-rw-r--r--docs/app.txt35
-rw-r--r--docs/appsh.dict1
-rwxr-xr-xtest/app-init.bats42
-rw-r--r--test/utils.bash11
-rw-r--r--tmp/0001-o-Supporting-platforms-where-xmlstarlet-is-installed.patch62
-rw-r--r--tmp/0002-o-Supporting-older-versions-of-asciidoc.patch115
-rw-r--r--tmp/0003-app-resolver-maven-Adding-support-for-artifacts-with.patch133
-rw-r--r--tmp/0004-lib-common-Changing-run_app-to-not-check-for-a-curre.patch53
-rw-r--r--tmp/0005-bin-app-upgrade-Removing-unreachable-code.-Fixing-it.patch248
15 files changed, 764 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
index 4acb46e..e420583 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ docs/*.xml
docs/*.html
docs/*.1
docs/*.7
+docs/*.txt.bak
.app/var
/*/.gitignore
diff --git a/bin/app-init b/bin/app-init
index bc4a0b2..bff92cf 100755
--- a/bin/app-init
+++ b/bin/app-init
@@ -9,13 +9,15 @@ export APPSH_HOME=$(cd $(dirname "$0")/.. && pwd)
# HEADER END
usage_text() {
- echo "usage: $usage_app -d dir <resolver> <resolver args>"
+ echo "usage: $usage_app [-s group.name=value] -d dir <resolver> <resolver args>"
}
dir=
prepend_config=
append_config=
-while getopts "d:C:c:" opt
+declare -A conf
+conf=()
+while getopts "d:C:c:s:" opt
do
case $opt in
d)
@@ -41,6 +43,19 @@ do
shift 2
OPTIND=1
;;
+ s)
+ keyvalue=$OPTARG
+ re="^$key_expr\\.$key_expr=.*$"
+ if [[ ! $keyvalue =~ $re ]]
+ then
+ usage "Invalid -s argument."
+ fi
+ key="${keyvalue%%=*}"
+ value="${keyvalue#*=}"
+ conf[$key]="$value"
+ shift 2
+ OPTIND=1
+ ;;
esac
done
@@ -77,6 +92,12 @@ trap '[[ $ok == yes ]] || rm -rf "$clean_dir"' EXIT
cd "$dir"
+for key in "${!conf[@]}"
+do
+ app-conf set $key "${conf[$key]}"
+done
+unset IFS
+
app-conf set app.resolver "$resolver_name"
"$resolver" init "$@"
diff --git a/docs/Makefile b/docs/Makefile
index 355f0bc..9ad7712 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -2,12 +2,12 @@ TXT=$(wildcard *.txt)
# Expand target section from heading of each page
MAN=$(shell ls *.txt|xargs -n 1 head -n 1|sed "s,\(.*\)(\([0-9]\)),\1.\2,")
HTML=$(patsubst %.txt,%.html,$(TXT))
+ASPELL=aspell
all: html man
html: $(HTML)
man: $(MAN)
-
.PHONY: html man
%.html: %.txt
@@ -25,5 +25,10 @@ define man
@a2x --format manpage $(1)
endef
+spell: $(patsubst %,spell-%,$(TXT))
+.PHONY: spell
+spell-%:
+ $(ASPELL) check -p appsh.dict -l en --encoding utf-8 $(patsubst spell-%,%,$@)
+
clean:
rm -rf $(wildcard *.html) $(wildcard *.1) $(wildcard *.7)
diff --git a/docs/app-cat-conf.txt b/docs/app-cat-conf.txt
index 57fe693..3b5501c 100644
--- a/docs/app-cat-conf.txt
+++ b/docs/app-cat-conf.txt
@@ -3,7 +3,7 @@ app-cat-conf(1)
NAME
----
-app-cat-conf - outputs a combined configuration file in a parseable
+app-cat-conf - outputs a combined configuration file in a parsable
format
SYNOPSIS
@@ -19,14 +19,14 @@ output a key once, the last value for each key seen will win.
A configuration file is a collection of grouped keys and values,
similar to Git.
-The keys consits of two parts: a _group_ and a _name_. Both have to
+The keys consist of two parts: a _group_ and a _name_. Both have to
match the regex `[a-zA-Z0-9]` and are combined with a dot.
OPTIONS
-------
-f::
- The config file to use. If the value is '-', stdin will be used as
- input. The option can be given multiple times.
+ The configuration file to use. If the value is '-', stdin will be
+ used as input. The option can be given multiple times.
-D::
Disables the inclusion of the default files.
-k::
@@ -37,9 +37,10 @@ OPTIONS
LOCATIONS AND DEFAULT FILES
---------------------------
-'app-cat-conf' will by default look for configuration files in three places:
+'app-cat-conf' will by default look for configuration files in three
+places:
-* '$APPSH_HOME/lib/default-config'
+* '$APPSH_HOME/lib/default-configuration'
* '$HOME/.appconfig'
@@ -60,13 +61,13 @@ Support _default values_: when a key is missing, return the default
value instead. Suggested option: '-d'.
+
Support _$APP_HOME_: If '$APP_HOME' is set, use that when reading the
-app's config values. Makes it easier to script as the user may move
-around directories, but still read values from the right place.
+app's configuration values. Makes it easier to script as the user may
+move around directories, but still read values from the right place.
SEE ALSO
--------
-git-config(1)
+git-configuration(1)
APP.SH
------
diff --git a/docs/app-init.txt b/docs/app-init.txt
new file mode 100644
index 0000000..58b7a7c
--- /dev/null
+++ b/docs/app-init.txt
@@ -0,0 +1,58 @@
+app-init(1)
+===========
+
+NAME
+----
+app-init - Installs an application
+
+SYNOPSIS
+--------
+[verse]
+'app-init' [-s group.name=key ...] -d <dir> <resolver> <resolver args>
+
+DESCRIPTION
+-----------
+
+Similar to 'git clone', 'app-init' is the first command you use when
+you want to deploy an application. It performs the following tasks:
+
+1. Create the '.app' directory and the config file.
+2. Initialize the resolver
+3. Run 'app upgrade' to install the initial version. 'app upgrade'
+ will also run any hooks defined in the application.
+
+OPTIONS
+-------
+
+-d::
+ The directory to create the application in. If the initialization
+ fails, the directory will be removed.
+-s group.name=key::
+ Add a configuration parameter before the resolver and any hooks are
+ fired.
++
+This option can be given multiple times.
+<resolver>::
+ The name of the resolver to use. 'app-init' will search the path for
+ an executable called 'app-resolver-<resolver>'.
+<resolver args>::
+ A list of arguments passed on directly to the resolver. See the
+ documentation of the resolver you're using for more details.
+
+BUILT-IN RESOLVERS
+------------------
+
+Appsh comes with two built-it resolvers:
+
+maven::
+ See linkman:app-resolver-maven[1].
+file::
+ See linkman:app-resolver-file[1].
+
+APP.SH
+------
+
+Part of the linkman:app[1] suite.
+
+// vim: set ft=asciidoc:
+
diff --git a/docs/app-install-file.txt b/docs/app-install-file.txt
index bb7cc48..e7525d1 100644
--- a/docs/app-install-file.txt
+++ b/docs/app-install-file.txt
@@ -8,7 +8,7 @@ app-install-file - Low-level installation of an app
SYNOPSIS
--------
[verse]
-'app-operator-pid' ...
+'app-install-file' ...
TODOs
-----
diff --git a/docs/app.txt b/docs/app.txt
index 9e9afc0..38cf169 100644
--- a/docs/app.txt
+++ b/docs/app.txt
@@ -26,7 +26,7 @@ $ app restart
---------------------------------------------------------------------
appsh is a pragmatic approach to managing a set of apps on a server.
-It is heavily inspired by git's approach in its command line interface
+It is heavily inspired by Git's approach in its command line interface
and scriptability.
It handles installation aspects: downloading, unpacking and upgrading,
@@ -38,7 +38,7 @@ command line, or through your own extensions.
Requirements
^^^^^^^^^^^^
-* Linux. OSX and Cygwin are possible to support, but it's not tested
+* Linux. OS X and Cygwin are possible to support, but it's not tested
there yet. Solaris is also doable.
* Bash 4 and "standard" GNU userland.
* If using Maven: xmlstarlet
@@ -46,7 +46,7 @@ Requirements
INSTALLING AN APPLICATION
~~~~~~~~~~~~~~~~~~~~~~~~~
-This resolved and downloads an appliaction from a Maven repository:
+This resolved and downloads an application from a Maven repository:
---------------------------------------------------------------------
$ app init -d my-app maven org.example:my-app:1.0-SNAPSHOT
@@ -89,13 +89,13 @@ and if that has changed, it will download and install the new version.
CREATING APPS
-------------
-An "app" is in itself nothing more than a zip archive with a particular
-layout. In the root of the zip archive there must be a directory called
-`root`. You can also place a file called `app.config` at the root. The
-config file will be imported into the app's configuration. It is also
-possible to run appliations before and after appliations are installed
-through hooks. These are placed in a `hooks` directory, also at the
-root of the archive.
+An "app" is in itself nothing more than a zip archive with a
+particular layout. In the root of the zip archive there must be a
+directory called `root`. You can also place a file called `app.config`
+at the root. The configuration file will be imported into the app's
+configuration. It is also possible to run applications before and
+after applications are installed through hooks. These are placed in a
+`hooks` directory, also at the root of the archive.
To summarize, this is what an application zip archive looks like:
@@ -125,18 +125,18 @@ CREATING LAUNCHERS
Trick when you don't know why your app won't start:
---------------------------------------------------------------------
-exec 1>/tmp/myapp.out
-exec 2>/tmp/myapp.err
+exec 1>/tmp/my-app.out
+exec 2>/tmp/my-app.err
---------------------------------------------------------------------
Make sure you _always_ use `exec` when spawning the actual app. This
-makes sure that the pid operator records the correct PID. If you don't
+makes sure that the PID operator records the correct PID. If you don't
do this the application will run, but will be reported as crashed when
you run 'app status'.
-If you can't use `exec` or the application demands to deamonize itself
+If you can't use `exec` or the application demands to demonize itself
(like Apache Httpd), you have to set the configuration option
-`app.pid_management=launcher`. Then the launcher is responsible for
+`app.PID_management=launcher`. Then the launcher is responsible for
creating the PID file under $APP_HOME/.app/pid. You can create a
symlink to the actual PID file if you can't customize where the app
places the file.
@@ -160,7 +160,8 @@ SEE ALSO
linkman:app-cat-conf[1],
linkman:app-conf[1],
linkman:app-install-file[1],
-linkman:app-operator-pid[1],
-linkman:appinternals[1]
+linkman:app-init[1],
+linkman:app-operator-PID[1],
+linkman:appinternals[7]
// vim: set ft=asciidoc:
diff --git a/docs/appsh.dict b/docs/appsh.dict
new file mode 100644
index 0000000..aef5c87
--- /dev/null
+++ b/docs/appsh.dict
@@ -0,0 +1 @@
+personal_repl-1.1 en 0
diff --git a/test/app-init.bats b/test/app-init.bats
index 8ffe1b6..2b5d724 100755
--- a/test/app-init.bats
+++ b/test/app-init.bats
@@ -3,20 +3,22 @@
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 "Invalid resolver" {
+ check_status=no
+ app init -d my-app wat
+ 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 "Already installed" {
+ mkdir -p my-app/.app
+ check_status=no
+ app init -d my-app maven
+ eq '$status' 1
+ eq '${#lines[*]}' 1
+ match '${lines[0]}' "my-app"
+}
@test "Happy day" {
mkzip app-a
@@ -74,3 +76,17 @@ load utils
match '${lines[0]}' ".*/versions/1.0/root$"
eq '${#lines[*]}' 1
}
+
+@test "app-init: Can pass configuration variables" {
+ mkzip app-a
+ app init -d my-app \
+ -s "foo.bar=awesome" \
+ -s "foo.baz=i love space" \
+ -s "foo.wat=2+2=5" file $APPSH_HOME/test/data/app-a.zip
+ cd my-app
+ app cat-conf -g foo
+ match '${lines[0]}' "foo.bar=awesome"
+ match '${lines[1]}' "foo.baz=i love space"
+ match '${lines[2]}' "foo.wat=2\+2=5"
+ eq '${#lines[*]}' 3
+}
diff --git a/test/utils.bash b/test/utils.bash
index 9979404..930b0f0 100644
--- a/test/utils.bash
+++ b/test/utils.bash
@@ -39,11 +39,12 @@ echo_lines() {
}
mkzip() {
-(
- cd $BATS_TEST_DIRNAME/data/$1
- rm -f ../$1.zip
- zip -qr ../$1.zip *
-)
+ local name=$1; shift
+ pushd .
+ cd $BATS_TEST_DIRNAME/data/$name
+ rm -f ../$name.zip
+ zip -qr ../$name.zip *
+ popd
}
install_artifact() {
diff --git a/tmp/0001-o-Supporting-platforms-where-xmlstarlet-is-installed.patch b/tmp/0001-o-Supporting-platforms-where-xmlstarlet-is-installed.patch
new file mode 100644
index 0000000..dc82a70
--- /dev/null
+++ b/tmp/0001-o-Supporting-platforms-where-xmlstarlet-is-installed.patch
@@ -0,0 +1,62 @@
+From ed52962997364560d341e0197c20a616e9b0f03e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= <trygvis@inamo.no>
+Date: Wed, 30 Oct 2013 14:29:06 +0100
+Subject: [PATCH 1/5] o Supporting platforms where 'xmlstarlet' is installed as
+ 'xml' (At least Suse does that).
+
+---
+ libexec/app-resolver-maven | 38 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 38 insertions(+)
+
+diff --git a/libexec/app-resolver-maven b/libexec/app-resolver-maven
+index 419adcc..ec1ad6b 100755
+--- a/libexec/app-resolver-maven
++++ b/libexec/app-resolver-maven
+@@ -81,6 +81,44 @@ download_artifact() {
+ fi
+ }
+
++
++which() {
++ /usr/bin/which "$1" 2>/dev/null
++}
++
++# Wrapper to cache the lookup of the xmlstarlet command.
++# Remember that xmlstarlet on at least SLES requires single quotes
++# instead of double quotes when building the selector, e.g.:
++# use [text()='zip'] instead of [text()="zip"].
++
++_xmlstarlet=""
++xmlstarlet() {
++ if [ ! -z "$_xmlstarlet" ]
++ then
++ "$_xmlstarlet" "$@"
++ return
++ fi
++
++ _xmlstarlet="`which xmlstarlet`"
++
++ if [ ! -z "$_xmlstarlet" ]
++ then
++ "$_xmlstarlet" "$@"
++ return
++ fi
++
++ _xmlstarlet="`which xml`"
++
++ if [ ! -z "$_xmlstarlet" ]
++ then
++ "$_xmlstarlet" "$@"
++ return
++ fi
++
++ echo "Could not find xmlstarlet." >&2
++ exit 1
++}
++
+ resolve_version() {
+ local group_id=`app-conf get maven.group_id`
+ local artifact_id=`app-conf get maven.artifact_id`
+--
+1.8.4.rc3
+
diff --git a/tmp/0002-o-Supporting-older-versions-of-asciidoc.patch b/tmp/0002-o-Supporting-older-versions-of-asciidoc.patch
new file mode 100644
index 0000000..f9869dd
--- /dev/null
+++ b/tmp/0002-o-Supporting-older-versions-of-asciidoc.patch
@@ -0,0 +1,115 @@
+From c106046cbb0cdb590035fcd33f5fc6ce1a3b975c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= <trygvis@inamo.no>
+Date: Thu, 31 Oct 2013 14:30:13 +0100
+Subject: [PATCH 2/5] o Supporting older versions of asciidoc.
+
+---
+ .gitignore | 1 +
+ docs/Makefile | 9 +++++++--
+ docs/appinternals.txt | 4 ++++
+ docs/asciidoc.conf | 35 +++++++++++++++++++++++++++++++++++
+ 4 files changed, 47 insertions(+), 2 deletions(-)
+
+diff --git a/.gitignore b/.gitignore
+index b6d1d32..4acb46e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -1,6 +1,7 @@
+ apps.list
+ downloads
+ target
++docs/*.xml
+ docs/*.html
+ docs/*.1
+ docs/*.7
+diff --git a/docs/Makefile b/docs/Makefile
+index f340323..355f0bc 100644
+--- a/docs/Makefile
++++ b/docs/Makefile
+@@ -3,11 +3,16 @@ TXT=$(wildcard *.txt)
+ MAN=$(shell ls *.txt|xargs -n 1 head -n 1|sed "s,\(.*\)(\([0-9]\)),\1.\2,")
+ HTML=$(patsubst %.txt,%.html,$(TXT))
+
+-all: $(HTML) $(MAN)
++all: html man
++
++html: $(HTML)
++man: $(MAN)
++
++.PHONY: html man
+
+ %.html: %.txt
+ @echo asciidoc $<
+- @asciidoc -f asciidoc.conf --backend=html5 $<
++ @asciidoc -f asciidoc.conf --backend=xhtml11 -aappsh_version=0.2-dev $<
+
+ %.1: %.txt
+ $(call man,$<)
+diff --git a/docs/appinternals.txt b/docs/appinternals.txt
+index ce19923..330d151 100644
+--- a/docs/appinternals.txt
++++ b/docs/appinternals.txt
+@@ -5,6 +5,10 @@ NAME
+ ----
+ appinternals - Appsh internals
+
++SYNOPSIS
++--------
++appinternals
++
+ DESCRIPTION
+ -----------
+
+diff --git a/docs/asciidoc.conf b/docs/asciidoc.conf
+index 724524a..57eaf59 100644
+--- a/docs/asciidoc.conf
++++ b/docs/asciidoc.conf
+@@ -1,6 +1,31 @@
++# https://github.com/marcelocantos/zeromq2-1/blob/master/doc/asciidoc.conf
++# http://lxr.free-electrons.com/source/tools/perf/Documentation/asciidoc.conf
++
+ [macros]
+ (?su)[\\]?(?P<name>linkman):(?P<target>\S*?)\[(?P<attrlist>.*?)\]=
+
++ifdef::doctype-manpage[]
++ifdef::backend-docbook[]
++[header]
++template::[header-declarations]
++<refentry>
++ <refmeta>
++ <refentrytitle>{mantitle}</refentrytitle>
++ <manvolnum>{manvolnum}</manvolnum>
++ <refmiscinfo class="source">app.sh</refmiscinfo>
++ <!-- doesn't seem to have any effect
++ <refmiscinfo class="version">version={appsh_version}</refmiscinfo>
++ -->
++ <refmiscinfo class="manual">App.sh Manual</refmiscinfo>
++ </refmeta>
++ <refnamediv>
++ <refname>{manname}</refname>
++ <refpurpose>{manpurpose}</refpurpose>
++ </refnamediv>
++# No ending refentry, asciidoc takes care of that.
++endif::backend-docbook[]
++endif::doctype-manpage[]
++
+ ifdef::backend-docbook[]
+ [linkman-inlinemacro]
+ {0%{target}}
+@@ -18,3 +43,13 @@ relative-ext=.html
+ <a href="{target}.html">{target}{0?({0})}</a>
+
+ endif::backend-html5[]
++
++ifdef::backend-xhtml11[]
++
++[attributes]
++relative-ext=.html
++
++[linkman-inlinemacro]
++<a href="{target}.html">{target}{0?({0})}</a>
++
++endif::backend-xhtml11[]
+--
+1.8.4.rc3
+
diff --git a/tmp/0003-app-resolver-maven-Adding-support-for-artifacts-with.patch b/tmp/0003-app-resolver-maven-Adding-support-for-artifacts-with.patch
new file mode 100644
index 0000000..2b7933f
--- /dev/null
+++ b/tmp/0003-app-resolver-maven-Adding-support-for-artifacts-with.patch
@@ -0,0 +1,133 @@
+From 746b90ad64e65b70bc902ed1bcd2c33ec7adf008 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= <trygvis@inamo.no>
+Date: Thu, 31 Oct 2013 16:27:09 +0100
+Subject: [PATCH 3/5] app-resolver-maven: Adding support for artifacts with
+ classifier.
+
+---
+ libexec/app-resolver-maven | 43 +++++++++++++++++++++++++++++++++----------
+ test/app-resolver-maven.bats | 27 +++++++++++++++++++++++++++
+ 2 files changed, 60 insertions(+), 10 deletions(-)
+ create mode 100755 test/app-resolver-maven.bats
+
+diff --git a/libexec/app-resolver-maven b/libexec/app-resolver-maven
+index ec1ad6b..4e21c4b 100755
+--- a/libexec/app-resolver-maven
++++ b/libexec/app-resolver-maven
+@@ -12,6 +12,8 @@ usage_text() {
+ echo "usage: $usage_app init -r <repo> <maven url>"
+ echo "usage: $usage_app resolve-version"
+ echo "usage: $usage_app download-version -v <version> -f <download target>"
++ echo "Maven url can be one of: <group id>:<artifact id>:<version> and"
++ echo "<group id>:<artifact id>:<classifier>:<version>"
+ }
+
+ slash() {
+@@ -122,6 +124,7 @@ xmlstarlet() {
+ resolve_version() {
+ local group_id=`app-conf get maven.group_id`
+ local artifact_id=`app-conf get maven.artifact_id`
++ local classifier=`app-conf get app.classifier`
+ local version=`app-conf get app.version`
+
+ repo=`app-conf get maven.repo`
+@@ -199,15 +202,17 @@ download_version() {
+ repo=`app-conf get maven.repo`
+ group_id=`app-conf get maven.group_id`
+ artifact_id=`app-conf get maven.artifact_id`
++ classifier=`app-conf get maven.classifier`
+ version=`app-conf get app.version`
+
+ group_id_slash=`slash $group_id`
+ base_path=$group_id_slash/$artifact_id/$version
++ file_name=$artifact_id-$resolved_version${classifier:+-}$classifier.zip
+
+ 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
++ l=.app/cache/$base_path/$file_name
++ r=$repo/$base_path/$file_name
+
+ echo "Downloading $group_id:$artifact_id:$resolved_version..."
+ get $r $l
+@@ -236,17 +241,35 @@ init() {
+ x=${x//:/ }
+ set -- $x
+
+- if [[ $# != 3 || $1 == "" || $2 == "" || $3 == "" ]]
+- then
+- usage "Invalid Maven coordinates: $coordinates"
+- fi
+-
+- group_id=$1
+- artifact_id=$2
+- version=$3
++ case $# in
++ 3)
++ if [[ $1 == "" || $2 == "" || $3 == "" ]]
++ then
++ usage "Invalid Maven coordinates: $coordinates"
++ fi
++ group_id=$1; shift
++ artifact_id=$1; shift
++ classifier=
++ version=$1; shift
++ ;;
++ 4)
++ if [[ $1 == "" || $2 == "" || $3 == "" || $4 == "" ]]
++ then
++ usage "Invalid Maven coordinates: $coordinates"
++ fi
++ group_id=$1; shift
++ artifact_id=$1; shift
++ classifier=$1; shift
++ version=$1; shift
++ ;;
++ *)
++ usage "Invalid Maven coordinates: $coordinates"
++ ;;
++ esac
+
+ app-conf set maven.group_id "$group_id"
+ app-conf set maven.artifact_id "$artifact_id"
++ [[ ! -z $classifier ]] && app-conf set maven.classifier "$classifier"
+ app-conf set app.version "$version"
+ }
+
+diff --git a/test/app-resolver-maven.bats b/test/app-resolver-maven.bats
+new file mode 100755
+index 0000000..e5a5474
+--- /dev/null
++++ b/test/app-resolver-maven.bats
+@@ -0,0 +1,27 @@
++#!/usr/bin/env bats
++# vim: set filetype=sh:
++
++load utils
++
++@test "plain init" {
++ mkdir .app
++ app resolver-maven init my-group:my-artifact:1.0-SNAPSHOT
++ app cat-conf
++ eq '${lines[0]}' "app.version=1.0-SNAPSHOT"
++ eq '${lines[1]}' "maven.artifact_id=my-artifact"
++ eq '${lines[2]}' "maven.group_id=my-group"
++ eq '${lines[3]}' "maven.repo=http://repo1.maven.org"
++ eq '${#lines[*]}' 4
++}
++
++@test "init with classifier" {
++ mkdir .app
++ app resolver-maven init my-group:my-artifact:app:1.0-SNAPSHOT
++ app cat-conf
++ eq '${lines[0]}' "app.version=1.0-SNAPSHOT"
++ eq '${lines[1]}' "maven.artifact_id=my-artifact"
++ eq '${lines[2]}' "maven.classifier=app"
++ eq '${lines[3]}' "maven.group_id=my-group"
++ eq '${lines[4]}' "maven.repo=http://repo1.maven.org"
++ eq '${#lines[*]}' 5
++}
+--
+1.8.4.rc3
+
diff --git a/tmp/0004-lib-common-Changing-run_app-to-not-check-for-a-curre.patch b/tmp/0004-lib-common-Changing-run_app-to-not-check-for-a-curre.patch
new file mode 100644
index 0000000..48e23de
--- /dev/null
+++ b/tmp/0004-lib-common-Changing-run_app-to-not-check-for-a-curre.patch
@@ -0,0 +1,53 @@
+From 90bc5af891b745f97196310f2bc4afd081daac3e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= <trygvis@inamo.no>
+Date: Fri, 1 Nov 2013 15:48:58 +0100
+Subject: [PATCH 4/5] lib/common: Changing run_app to not check for a 'current'
+ link if version is given. This will unbreak hooks that are run before a
+ current link is installed or if it has been removed.
+
+---
+ lib/common | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/lib/common b/lib/common
+index 369f0be..c2babd2 100755
+--- a/lib/common
++++ b/lib/common
+@@ -2,6 +2,7 @@
+
+ # Asserts that the cwd is an app directory.
+ # By default it checks that there is a 'current' link.
++# TODO: make check_link default to no if version is set
+ assert_is_app() {
+ local check_link=yes
+ local version=
+@@ -241,8 +242,8 @@ run_app() {
+ case $opt in
+ v)
+ version=$OPTARG
+- shift 2
+- OPTIND=1
++ shift 2
++ OPTIND=1
+ ;;
+ esac
+ done
+@@ -258,13 +259,13 @@ run_app() {
+ assert_is_app
+ cd current
+ else
+- assert_is_app -v "$version"
++ assert_is_app -C -v "$version"
+ cd "versions/$version"
+ fi
+
+ path=/bin:/usr/bin
+
+- # This magically get the expansion of $u correct.
++ # This will magically get the expansion of $u correct.
+ IFS="
+ "
+
+--
+1.8.4.rc3
+
diff --git a/tmp/0005-bin-app-upgrade-Removing-unreachable-code.-Fixing-it.patch b/tmp/0005-bin-app-upgrade-Removing-unreachable-code.-Fixing-it.patch
new file mode 100644
index 0000000..0123feb
--- /dev/null
+++ b/tmp/0005-bin-app-upgrade-Removing-unreachable-code.-Fixing-it.patch
@@ -0,0 +1,248 @@
+From 274b7d15a82351dfee32be32bade4e35246d5beb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= <trygvis@inamo.no>
+Date: Fri, 1 Nov 2013 16:24:26 +0100
+Subject: [PATCH 5/5] bin/app-upgrade: Removing unreachable code. Fixing it so
+ it compares against the currently installed version instead of the last
+ resolved version which makes it possible to retry installation of the same
+ version. libexec/app-install-file: Allowing installation of the same file
+ twice. Checks if the file has been unpacked earlier or not. Removes the
+ unpacked version on failure.
+
+---
+ bin/app-upgrade | 36 +++++++++++++++---------------------
+ libexec/app-install-file | 30 +++++++++++++++++++-----------
+ libexec/app-resolver-file | 2 +-
+ test/app-init.bats | 14 ++++++++------
+ test/app-upgrade.bats | 30 ++++++++++++++++++++++++++++++
+ test/data/app-a/hooks/post-install | 8 +++++++-
+ test/data/app-a/hooks/pre-install | 11 +++++++++++
+ 7 files changed, 91 insertions(+), 40 deletions(-)
+ create mode 100755 test/data/app-a/hooks/pre-install
+
+diff --git a/bin/app-upgrade b/bin/app-upgrade
+index 1a8329a..db40f4e 100755
+--- a/bin/app-upgrade
++++ b/bin/app-upgrade
+@@ -19,40 +19,34 @@ fi
+
+ assert_is_app
+
++version=`app-conf get app.version`
++installed_version=`app-conf get app.installed_version`
++
++# TODO: should this explicitly check for a discrepancy between
++# resolved_version and installed_version? This indicates an app that's
++# not completely installed.
++
++# Find the resolver and resolve the version
+ resolver_name=`app-conf get app.resolver`
+ resolver=`find_resolver "$resolver_name"`
+
+-old_version=`app-conf get app.resolved_version`
+-echo "Resolving version $old_version"
++echo "Resolving version $version"
+ "$resolver" resolve-version
+-new_version=`app-conf get app.resolved_version`
++resolved_version=`app-conf get app.resolved_version`
+
+-if [[ $new_version == $old_version ]]
++if [[ $resolved_version == $installed_version ]]
+ then
+ echo "No new version available" 2>&1
+ exit
+ fi
+
+-echo "Resolved version to $new_version"
+-
+-if [ "$new_version" = "" ]
+-then
+- new_version=`app-conf get app.resolved_version`
+-fi
+-
+-if [ "$new_version" = "" ]
++if [ "$resolved_version" = "" ]
+ then
+ fatal "app.resolved_version is not set."
+ fi
+
+-installed_version=`app-conf get app.installed_version`
+-
+-if [ "$new_version" = "$installed_version" ]
+-then
+- echo "$new_version is already installed"
+- exit 0
+-fi
++echo "Resolved version to $resolved_version"
+
+-"$resolver" download-version -v "$new_version" -f .app/latest.zip
++"$resolver" download-version -v "$resolved_version" -f .app/latest.zip
+
+-app-install-file -v "$new_version" -f .app/latest.zip
++app-install-file -v "$resolved_version" -f .app/latest.zip
+diff --git a/libexec/app-install-file b/libexec/app-install-file
+index c31f1f4..32b0407 100755
+--- a/libexec/app-install-file
++++ b/libexec/app-install-file
+@@ -48,21 +48,29 @@ then
+ usage
+ fi
+
+-if [ -d versions/$version ]
++re="[.a-zA-Z0-9]"
++
++if [[ ! $version =~ $re ]]
+ then
+- echo "Version $version is already installed"
+- exit 1
++ fatal "Invalid version: $version"
+ fi
+
+-mkdir -p versions/$version
+-
+-echo "Unpacking..."
+-unzip -q -d versions/$version $file
+-
+-if [ ! -d versions/$version/root ]
++if [ -d versions/$version ]
+ then
+- echo "Invalid zip file, did not contain a ./root directory." >&2
+- exit 1
++ echo "Version $version is already unpacked"
++else
++ mkdir -p versions/$version.tmp
++
++ echo "Unpacking..."
++ unzip -q -d versions/$version.tmp $file
++
++ if [ ! -d versions/$version.tmp/root ]
++ then
++ echo "Invalid zip file, did not contain a 'root' directory." >&2
++ rm -rf versions/$version.tmp
++ exit 1
++ fi
++ mv versions/$version.tmp versions/$version
+ fi
+
+ if [ -n "$prepend_config" ]
+diff --git a/libexec/app-resolver-file b/libexec/app-resolver-file
+index 2b708e8..5972926 100755
+--- a/libexec/app-resolver-file
++++ b/libexec/app-resolver-file
+@@ -47,7 +47,7 @@ init() {
+ resolve_version() {
+ path=$(app-conf get file.path)
+
+- local s=$(stat -c %Z $path)
++ local s=$(stat -c %Y $path)
+
+ app-conf set app.resolved_version "$s"
+ }
+diff --git a/test/app-init.bats b/test/app-init.bats
+index 8ffe1b6..e4dfeae 100755
+--- a/test/app-init.bats
++++ b/test/app-init.bats
+@@ -29,9 +29,10 @@ load utils
+ match '${lines[2]}' "Downloading org.example:app-a:1.0-*"
+ eq '${lines[3]}' "Unpacking..."
+ match '${lines[4]}' "Importing config from versions/1.0-*"
+- match '${lines[5]}' "Creating current symlink for version 1.0-*"
+- eq '${lines[6]}' "Post install"
+- eq '${#lines[*]}' 7
++ eq '${lines[5]}' "pre-install"
++ match '${lines[6]}' "Creating current symlink for version 1.0-*"
++ eq '${lines[7]}' "post-install"
++ eq '${#lines[*]}' 8
+
+ is_directory "my-app/.app"
+ # Created by post-install
+@@ -48,9 +49,10 @@ load utils
+ match '${lines[1]}' "Downloading org.example:app-a:1.0-*"
+ eq '${lines[2]}' "Unpacking..."
+ match '${lines[3]}' "Importing config from versions/1.0-*"
+- match '${lines[4]}' "Creating current symlink for version 1.0-*"
+- eq '${lines[5]}' "Post install"
+- eq '${#lines[*]}' 6
++ eq '${lines[4]}' "pre-install"
++ match '${lines[5]}' "Creating current symlink for version 1.0-*"
++ eq '${lines[6]}' "post-install"
++ eq '${#lines[*]}' 7
+
+ is_directory "my-app/.app"
+ # Created by post-install
+diff --git a/test/app-upgrade.bats b/test/app-upgrade.bats
+index d27c6e7..9a282e3 100755
+--- a/test/app-upgrade.bats
++++ b/test/app-upgrade.bats
+@@ -38,3 +38,33 @@ load utils
+ describe new_resolved_version = $new_resolved_version
+ neq $new_resolved_version $resolved_version
+ }
++
++@test "app-upgrade - when pre-install fails the first run" {
++ mkzip app-a
++ file=$APPSH_HOME/test/data/app-a.zip
++ touch -t 01010101 $file
++
++ app init -d my-app file $file
++
++ cd my-app
++
++ # A new version is available, but make sure pre-install fails.
++ touch -t 02020202 $file
++ touch fail-pre-install
++ check_status=no
++ app upgrade
++ eq '${status}' 1
++
++ # Try to reinstall the same file
++ rm fail-pre-install
++ app upgrade
++ eq '${lines[0]}' "Resolving version "
++ eq '${lines[1]}' "Resolved version to 1359766920"
++ eq '${lines[2]}' "Version 1359766920 is already unpacked"
++ eq '${lines[3]}' "Importing config from versions/1359766920/app.config"
++ eq '${lines[4]}' "pre-install"
++ eq '${lines[5]}' "Changing current symlink from 1356998460 to 1359766920"
++ eq '${lines[6]}' "post-install"
++
++ eq '${#lines[*]}' 7
++}
+diff --git a/test/data/app-a/hooks/post-install b/test/data/app-a/hooks/post-install
+index 1dfb7be..0717f2a 100755
+--- a/test/data/app-a/hooks/post-install
++++ b/test/data/app-a/hooks/post-install
+@@ -2,7 +2,13 @@
+
+ set -u
+
+-echo "Post install"
++echo "post-install"
++
++if [[ -e $APP_HOME/fail-post-install ]]
++then
++ echo "Simulating failure."
++ exit 1
++fi
+
+ NAME=`basename $APP_HOME`
+
+diff --git a/test/data/app-a/hooks/pre-install b/test/data/app-a/hooks/pre-install
+new file mode 100755
+index 0000000..4b95ac4
+--- /dev/null
++++ b/test/data/app-a/hooks/pre-install
+@@ -0,0 +1,11 @@
++#!/bin/bash -e
++
++set -u
++
++echo "pre-install"
++
++if [[ -e $APP_HOME/fail-pre-install ]]
++then
++ echo "Simulating failure."
++ exit 1
++fi
+--
+1.8.4.rc3
+