~rcj/maas-images/ppc64el

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
#!/bin/bash

VERBOSITY=1
TEMP_D=""

info() { echo "$@" 1>&2; }
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }

Usage() {
    cat <<EOF
Usage: ${0##*/} [ options ] source-image output-image.gz

   convert 'source-image' into a ephemeral image and put
   output into output-image.gz.

   source-image can be url or file path that is either .tar.gz or .img.

   options:
         --no-gzip     do not gzip output image
    -k | --kernel K    keep/install the kernel package 'k' inside image
    -p | --krd-pack P  create a kernel / ramdisk pack
                       P is ',' delimited: kernel-name,kernel,ramdisk[,flags]
    -v | --verbose     increase verbosity

   Example:
     ${0##*/} --kernel=linux-generic \\
        --krd-pack=linux-generic,output/kernel,output/initrd \\
        trusty-server-cloudimg-armhf.tar.gz \\
        output/root-image.gz
EOF
}

bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
    [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}

ddebug() {
    local level="$1"; shift;
    [ "${level}" -gt "${VERBOSITY}" ] && return
    error "$(date -R):" "$@"
}

debug() {
    local level=${1}; shift;
    [ "${level}" -gt "${VERBOSITY}" ] && return
    error "${@}"
}

get_img_from_tar() {
    local img="$1" out="$2" tempd="$3"
    local mtmp=$(mktemp -d "${tempd}/img_from_tar.XXXXXX")
    tar -C "$mtmp" -Sxf "$img" ||
        { error "failed to extract $img"; return 1; }
    local f="" found=""
    for f in "${mtmp}/"*; do
        [ ! -f "$f" -o "${f%.img}" = "$f" ] && continue
        [ -z "$found" ] ||
            { error "multiple .img found in $img"; return 1; }
        found="$f"
    done
    [ -n "$found" ] || { error "no .img in $img"; return 1; }
    mv "$found" "$out" && rm -Rf "$mtmp"
}

get_img_file() {
    local fout="" input="$1" output="$2" tempd="$3"
    fout=$(LANG=C file "$input") || { error "failed: file $input"; return 1; }
    case "${fout#$input: }" in
        "gzip compressed"*)
            fout=$(zcat "$input" | file -)
            case "${fout#*: }" in
                POSIX\ tar*) get_img_from_tar "$input" "$output" "$tempd";;
                *) zcat -c "$input" > "$output";;
            esac
            ;;
        "POSIX tar"*)
            get_img_from_tar "$input" "$output" "$tempd";;
        *ext[234]\ filesystem*)
            ln -s "$(readlink -f "$input")" "$output";;
        *)
            # if the above failed (on trusty a .tar.gz file was reported
            # as a Minux file system) then try filename based heuristics
            case "$input" in
                *.tar.gz|*.tgz|*.tar) 
                    debug 1 "guessing content is tar archive [$fout]"
                    get_img_from_tar "$input" "$output" "$tempd";;
                *.gz)
                    debug 1 "guessing content is gzip [$fout]"
                    zcat -c "$input" > "$output";;
                *) 
                    error "WARN: unknown file '$input'. Assuming mountable image"
                    error "file output: $fout"
                    ln -s "$(readlink -f "$input")" "$output";;
            esac
    esac
}

ensure_file_d() {
    local f="" d=""
    for f in "$@"; do
        d=$(dirname "$f")
        mkdir -p "$d" || return
    done
    return 0
}

# Grow the image container and the filesystem within to the specified # of MB
# Requirements:
#  The image container must be raw format.
#  The image container has no partitioning.
#  The filesystem in the container must be ext2/3/4.
grow_img() {
    local img="$1" size_MB="$2"

    # Check that this is a raw image
    local img_fmt=$(qemu-img info --output=json ${img} | \
        python -c 'import json,sys;obj=json.load(sys.stdin);print obj["format"];')
    if [ "${img_fmt}" != "raw" ] ; then
        error "Expected 'raw' image format, found '${img_fmt}'.  Can not resize"
        return 1
    fi

    # Get current size of image
    local img_bytes=$(qemu-img info --output=json ${img} | \
        python -c 'import json,sys;obj=json.load(sys.stdin);print obj["virtual-size"];')

    size_bytes=$(($size_MB * 1024 * 1024))
    if [ ${size_bytes} -lt ${img_bytes} ] ; then
        info "Will not shrink image ${img} from ${img_bytes} to ${size_bytes}"
        return 0
    fi

    info "Resizing image container to ${size_MB}MB"
    qemu-img resize ${img} ${size_MB}M

    info "Attaching loop device to image"
    LODEV=$(losetup --find --show ${img}) ||
        { error "failed to expand image to ${size_MB}M"; return 1; }
    [ -z "${LODEV}" ] && { error "could not setup loop device"; return 1; }

    info "Checking image filesystem"
    e2fsck -f -y ${LODEV} || { error "fsck failed for image"; return 1; }
    info "Resizing image filesystem to fill container"
    resize2fs ${LODEV} || { error "resize2fs failed on image"; return 1; }

    info "Detaching loop device for image"
    losetup --detach ${LODEV} || { error "loop detach failure"; return 1; }
}

main() {
    local short_opts="a:hk:p:v"
    local long_opts="arch:,help,kernel:,krd-pack:,no-gzip,verbose"
    local getopt_out=""
    getopt_out=$(getopt --name "${0##*/}" \
        --options "${short_opts}" --long "${long_opts}" -- "$@") &&
        eval set -- "${getopt_out}" ||
        { bad_Usage; return; }

    local cur="" next="" output="" gzip=true kpkg=""
    local kpacks="" src_in="" src="" arch="" vflags=""
    kpacks=( )

    while [ $# -ne 0 ]; do
        cur="$1"; next="$2";
        case "$cur" in
            -h|--help) Usage ; exit 0;;
            -a|--arch) arch="$next"; shift;;
            -k|--kernel) kpkg=$next; shift;;
            -p|--krd-pack) kpacks[${#kpacks[@]}]="$next"; shift;;
               --no-gzip) gzip=false;;
            -v|--verbose) VERBOSITY=$((${VERBOSITY}+1)); vflags="${vflags}v";;
            --) shift; break;;
        esac
        shift;
    done

    [ $# -eq 2 ] || { bad_Usage "must provide source and target"; return; }
    src_in="$1"
    output="$2"
    [ -z "$vflags" ] || vflags="-$vflags"

    PATH="$(dirname "$0"):$PATH"
    command -v "maas-cloudimg2ephemeral" >/dev/null 2>&1 ||
        { error "do not have maas-cloudimg2ephemeral in path"; return 1; }

    TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
        { error "failed to make tempdir"; return 1; }
    trap cleanup EXIT

    if [ -f "$src_in" ]; then
        src="$src_in"
    else
        case "$src_in" in
            http://*|https://*|ftp://*)
                ddebug 1 "getting source $src_in"
                src="${TEMP_D}/${src_in##*/}"
                wget "$src_in" --dot-style=mega -O "$src" ||
                    { error "failed download $src_in"; return 1; }
                ;;
            file://*)
                [ -f "${src_in#file://}" ] ||
                    { error "$src_in: not a file"; return 1; }
                src="${src_in#file://}"
                ;;
            *) error "Unable to handle src: $src_in"; return 1;;
        esac
    fi

    # now src is either a .tar.gz file, or a .img file
    local imgfile="${TEMP_D}/root.img"
    ddebug 1 "getting .img from $src"
    get_img_file "$src" "$imgfile" "${TEMP_D}" ||
        { error "failed to get image file from $src"; return 1; }

    if [ "$src" -ef "$imgfile" ]; then
        rm -f "$imgfile"
        ddebug 1 "copying $src_in to $imgfile"
        cp --sparse=always "$src" "$imgfile" ||
            { error "failed to copy $src_in to temp"; return 1; }
    fi

    ensure_file_d "$output" ||
        { error "failed to create dir for $output"; return 1; }

    # Resize to 2G
    grow_img ${imgfile} 2048
    
    # now imgfile has the root filesystem image to adjust
    local pkout="${TEMP_D}/$kpkg-kernel" piout="${TEMP_D}/$kpkg-initrd"
    local manif="${TEMP_D}/manifest"
    ddebug 1 "starting maas-cloudimg2ephemeral"
    maas-cloudimg2ephemeral $vflags --zero \
        ${arch:+"--arch=$arch"} "$imgfile" \
        "$kpkg" "$pkout" "$piout" "$manif" ||
        { error "failed to turn $imgfile to ephemeral"; return 1; }

    local i="" ckpkg="" ckout="" ciout="" subarch="" oifs="$IFS"
    ddebug 1 "starting kpacks: ${kpacks[*]}"
    for i in "${kpacks[@]}"; do
        IFS=","; set -- $i; IFS="$oifs"
        # tkout, tiout = target kernel out, target initrd out
        ckpkg="$1"; tkout="$2"; tiout="$3"
        shift 3
        flags=( "$@" )
        ckout="${TEMP_D}/$ckpkg-kernel"
        ciout="${TEMP_D}/$ckpkg-initrd"
        ddebug 1 "$(date -R): starting kpack-from-image for $ckpkg/${flags[*]}"
        KPACK_SKIP_APT_UPDATE=true kpack-from-image ${vflags} "$imgfile" \
            "$ckpkg" "$ckout" "$ciout" "${flags[@]}" ||
            { error "failed to get $ckpkg output"; return 1; }
        ensure_file_d "$tkout" "$tiout" ||
            { error "failed to make dirs for $tkout or $tiout"; return 1; }
        mv "$ckout" "$tkout" &&
            mv "$ciout" "$tiout" ||
            { error "failed copying files"; return 1; }
    done
    ddebug 1 "kpacks done"

    if $gzip; then
        ddebug 1 "gzipping image file"
        local stime=$SECONDS
        gzip -9 --rsyncable "$imgfile" &&
            mv --force "$imgfile.gz" "$output" ||
            { error "failed gzip"; return 1; }
        ddebug 1 "gzip took $(($SECONDS-$stime))s"
    else
        mv --force "$imgfile" "$output" ||
            { error "failed move to $output"; return 1; }
    fi
    ddebug 1 "finished"
    return 0
}

main "$@"
# vi: ts=4 expandtab