Cross package maker. DEB/RPM generation or conversion. Derived from jordansissel/fpm.

⌈⌋ ⎇ branch:  cross package maker


Check-in [42154b78cd]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add a self-extracting sh package type implementation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 42154b78cd799a0df057c0217d48d0d344966bd2
User & Date: chris.gerber@tapjoy.com 2014-03-11 04:40:04
Context
2014-03-11
04:40
Fix FORCE variable handling; add post install logging check-in: c32d903019 user: chris.gerber@tapjoy.com tags: trunk
04:40
Add a self-extracting sh package type implementation check-in: 42154b78cd user: chris.gerber@tapjoy.com tags: trunk
2014-02-10
23:54
Update changelist check-in: 0e48f9977b user: jls@semicomplete.com tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added lib/fpm/package/sh.rb.























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
require "erb"
require "fpm/namespace"
require "fpm/package"
require "fpm/errors"
require "fpm/util"
require "backports"
require "fileutils"
require "digest"

# Support for self extracting sh files (.sh files)
#
# This class only supports output of packages.
#
# The sh package is a single sh file with a bzipped tar payload concatenated to the end.
# The script can unpack the tarball to install it and call optional post install scripts.
class FPM::Package::Sh < FPM::Package

  def output(output_path)
    create_scripts

    # Make one file. The installscript can unpack itself.
    `cat #{install_script} #{payload} > #{output_path}`
    FileUtils.chmod("+x", output_path)
  end

  def create_scripts
    if script?(:before_install)
      # the scripts are kept in the payload so what would before install be if we've already
      # unpacked the payload?
      raise "sh package does not support before install scripts."
    end

    if script?(:after_install)
      File.write(File.join(fpm_meta_path, "after_install"), script(:after_install))
    end
  end

  def install_script
    path = build_path("installer.sh")
    File.open(path, "w") do |file|
      file.write template("sh.erb").result(binding)
    end
    path
  end

  # Returns the path to the tar file containing the packed up staging directory
  def payload
    payload_tar = build_path("payload.tar")
    @logger.info("Creating payload tar ", :path => payload_tar)

    args = [ tar_cmd,
             "-C",
             staging_path,
             "-cf",
             payload_tar,
             "--owner=0",
             "--group=0",
             "--numeric-owner",
             "." ]

    unless safesystem(*args)
      raise "Command failed while creating payload tar: #{args}"
    end
    payload_tar
  end

  # Where we keep metadata and post install scripts and such
  def fpm_meta_path
    @fpm_meta_path ||= begin
                         path = File.join(staging_path, ".fpm")
                         FileUtils.mkdir_p(path)
                         path
                       end
  end
end

Changes to lib/fpm/version.rb.

1
2
3
module FPM
  VERSION = "1.0.2"
end

|

1
2
3
module FPM
  VERSION = "1.1.0"
end

Added templates/sh.erb.



















































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/bin/bash

# bail out if any part of this fails
set -e

# This is the self-extracting installer script for an FPM shell installer package.
# It contains the logic to unpack a tar archive appended to the end of this script
# and, optionally, to run post install logic.
# Run the package file with -h to see a usage message or look at the print_usage method.
#
# The post install scripts are called with INSTALL_ROOT, INSTALL_DIR and VERBOSE exported
# into the environment for their use.
#
# INSTALL_ROOT = the path passed in with -i or a relative directory of the name of the package
#                file with no extension
# INSTALL_DIR  = the same as INSTALL_ROOT unless -c (capistrano release directory) argumetn
#                is used. Then it is $INSTALL_ROOT/releases/<datestamp>
# CURRENT_DIR  = if -c argument is used, this is set to the $INSTALL_ROOT/current which is
#                symlinked to INSTALL_DIR
# VERBOSE      = is set if the package was called with -v for verbose output
function main() {
    set_install_dir

    create_pid

    wait_for_others

    kill_others

    set_owner

    unpack_payload

    if [[ -n $UNPACK_ONLY ]] ; then
        echo "Unpacking complete, not moving symlinks or restarting because unpack only was specified."
    else
        create_symlinks

        set +e # don't exit on errors to allow us to clean up
        if ! run_post_install ; then
            revert_symlinks
            log "Installation failed."
            exit 1
        else
            clean_out_old_releases
            log "Installation complete."
        fi
    fi
}

# deletes the PID file for this installation
function delete_pid(){
    rm -f ${INSTALL_DIR}/$$.pid 2> /dev/null
}

# creates a PID file for this installation
function create_pid(){
    trap "delete_pid" EXIT
    echo $$> ${INSTALL_DIR}/$$.pid
}


# checks for other PID files and sleeps for a grace period if found
function wait_for_others(){
    local count=`ls ${INSTALL_DIR}/*.pid | wc -l`

    if [ $count -gt 1 ] ; then
        sleep 10
    fi
}

# kills other running installations
function kill_others(){
    for PID_FILE in $(ls ${INSTALL_DIR}/*.pid) ; do
        local p=`cat ${PID_FILE}`
        if ! [ $p == $$ ] ; then
            kill -9 $p
            rm -f $PID_FILE 2> /dev/null
        fi
    done
}

# echos metadata file. A function so that we can have it change after we set INSTALL_ROOT
function fpm_metadata_file(){
    echo "${INSTALL_ROOT}/.install-metadata"
}

# if this package was installed at this location already we will find a metadata file with the details
# about the installation that we left here. Load from that if available but allow command line args to trump
function load_environment(){
    local METADATA=$(fpm_metadata_file)
    if [ -r "${METADATA}" ] ; then
        log "Found existing metadata file '${METADATA}'. Loading previous install details. Env vars in current environment will take precedence over saved values."
        local TMP="/tmp/$(basename $0).$$.tmp"
        # save existing environment, load saved environment from previous run from install-metadata and then
        # overlay current environment so that anything set currencly will take precedence
        # but missing values will be loaded from previous runs.
        save_environment "$TMP"
        source "${METADATA}"
        source $TMP
        rm "$TMP"
    fi
}

# write out metadata for future installs
function save_environment(){
    local METADATA=$1
    echo -n "" > ${METADATA} # empty file

    # just piping env to a file doesn't quote the variables. This does
    # filter out multiline junk and _. _ is a readonly variable
    env | egrep "^[^ ]+=.*" | grep -v "^_=" | while read ENVVAR ; do
        local NAME=${ENVVAR%%=*}
        # sed is to preserve variable values with dollars (for escaped variables or $() style command replacement),
        # and command replacement backticks
        # Escaped parens captures backward reference \1 which gets replaced with backslash and \1 to esape them in the saved
        # variable value
        local VALUE=$(eval echo '$'$NAME | sed 's/\([$`]\)/\\\1/g')
        echo "export $NAME=\"$VALUE\"" >> ${METADATA}
    done

    if [ -n "${OWNER}" ] ; then
        chown ${OWNER} ${METADATA}
    fi
}

function set_install_dir(){
    # if INSTALL_ROOT isn't set by parsed args, use basename of package file with no extension
    DEFAULT_DIR=$(echo $(basename $0) | sed -e 's/\.[^\.]*$//')
    INSTALL_DIR=${INSTALL_ROOT:-$DEFAULT_DIR}

    DATESTAMP=$(date +%Y%m%d%H%M%S)
    if [ -z "$USE_FLAT_RELEASE_DIRECTORY" ] ; then
        RELEASE_ID=<%= release_id %>
        INSTALL_DIR="${RELEASES_DIR}/${RELEASE_ID:-$DATESTAMP}"
    fi

    mkdir -p "$INSTALL_DIR" || die "Unable to create install directory $INSTALL_DIR"

    export INSTALL_DIR

    log "Installing package to '$INSTALL_DIR'"
}

function set_owner(){
    export OWNER=${OWNER:-$USER}
    log "Installing as user $OWNER"
}

function unpack_payload(){
    if $FORCE || [ ! "$(ls -A $INSTALL_DIR)" ] ; then
        log "Unpacking payload . . ."
        local archive_line=$(grep -a -n -m1 '__ARCHIVE__$' $0 | sed 's/:.*//')
        tail -n +$((archive_line + 1)) $0 | tar -C $INSTALL_DIR -xf - > /dev/null || die "Failed to unpack payload from the end of '$0' into '$INSTALL_DIR'"
    else
        # Files are already here, just move symlinks
        log "Directory already exists and has contents ($INSTALL_DIR). Not unpacking payload."
    fi
}

function run_post_install(){
    local AFTER_INSTALL=$INSTALL_DIR/.fpm/after_install
    if [ -r $AFTER_INSTALL ] ; then
        chmod +x $AFTER_INSTALL
        log "Running post install script"
        $AFTER_INSTALL
        return $?
    fi
    return 0
}

function create_symlinks(){
    [ -n "$USE_FLAT_RELEASE_DIRECTORY" ] && return

    export CURRENT_DIR="$INSTALL_ROOT/current"
    if [ -e "$CURRENT_DIR" ] ; then
        OLD_CURRENT_TARGET=$(readlink $CURRENT_DIR)
        rm "$CURRENT_DIR"
    fi
    ln -s "$INSTALL_DIR" "$CURRENT_DIR"

    log "Symlinked '$INSTALL_DIR' to '$CURRENT_DIR'"
}

# in case post install fails we may have to back out switching the symlink to current
# We can't switch the symlink after because post install may assume that it is in the
# exact state of being installed (services looking to current for their latest code)
function revert_symlinks(){
    if [ -n "$OLD_CURRENT_TARGET" ] ; then
        log "Putting current symlink back to '$OLD_CURRENT_TARGET'"
        if [ -e "$CURRENT_DIR" ] ; then
            rm "$CURRENT_DIR"
        fi
        ln -s "$OLD_CURRENT_TARGET" "$CURRENT_DIR"
    fi
}

function clean_out_old_releases(){
    [ -n "$USE_FLAT_RELEASE_DIRECTORY" ] && return

    while [ $(ls -tr "${RELEASES_DIR}" | wc -l) -gt 2 ] ; do
        OLDEST_RELEASE=$(ls -tr "${RELEASES_DIR}" | head -1)
        log "Deleting old release '${OLDEST_RELEASE}'"
        rm -rf "${RELEASES_DIR}/${OLDEST_RELEASE}"
    done
}

function print_usage(){
    echo "Usage: `basename $0` [options]"
    echo "Install this package"
    echo "  -i <DIRECTORY> : install_root - an optional directory to install to."
    echo "      Default is package file name without file extension"
    echo "  -o <USER>     : owner - the name of the user that will own the files installed"
    echo "                   by the package. Defaults to current user"
    echo "  -r: disable capistrano style release directories - Default behavior is to create a releases directory inside"
    echo "      install_root and unpack contents into a date stamped (or build time id named) directory under the release"
    echo "      directory. Then create a 'current' symlink under install_root to the unpacked"
    echo "      directory once installation is complete replacing the symlink if it already "
    echo "      exists. If this flag is set just install into install_root directly"
    echo "  -u: Unpack the package, but do not install and symlink the payload"
    echo "  -f: force - Always overwrite existing installations"
    echo "  -y: yes - Don't prompt to clobber existing installations"
    echo "  -v: verbose - More output on installation"
    echo "  -h: help -  Display this message"
}

function die () {
    local message=$*
    echo "Error: $message : $!"
    exit 1
}

function log(){
    local message=$*
    if [ -n "$VERBOSE" ] ; then
        echo "$*"
    fi
}

function parse_args() {
    args=`getopt i:o:rfuyvh $*`

    if [ $? != 0 ] ; then
        print_usage
        exit 2
    fi
    set -- $args
    for i
    do
        case "$i"
            in
            -r)
                USE_FLAT_RELEASE_DIRECTORY=1
                shift;;
            -i)
                shift;
                export INSTALL_ROOT="$1"
                export RELEASES_DIR="${INSTALL_ROOT}/releases"
                shift;;
            -o)
                shift;
                export OWNER="$1"
                shift;;
            -v)
                export VERBOSE=1
                shift;;
            -u)
                UNPACK_ONLY=true
                shift;;
            -f)
                FORCE=true
                shift;;
            -y)
                CONFIRM="y"
                shift;;
            -h)
                print_usage
                exit 0
                shift;;
            --)
                shift; break;;
        esac
    done
}

# parse args first to get install root
parse_args $*
# load environment from previous installations so we get defaults from that
load_environment
# reparse args so they can override any settings from previous installations if provided on the command line
parse_args $*

main
save_environment $(fpm_metadata_file)
exit 0

__ARCHIVE__