jjb: Add jobs for building ci/dev images
authorKienan Stewart <kstewart@efficios.com>
Tue, 31 Oct 2023 13:57:03 +0000 (09:57 -0400)
committerKienan Stewart <kstewart@efficios.com>
Fri, 19 Jan 2024 19:59:34 +0000 (14:59 -0500)
Change-Id: I68ec852b8dcf4775966b8bfb4a53e1d539b58d2b
Signed-off-by: Kienan Stewart <kstewart@efficios.com>
automation/images/.gitkeep [new file with mode: 0644]
jobs/images.yml [new file with mode: 0644]
pipelines/images/default.groovy [new file with mode: 0644]
pipelines/images/distrobuild.sh [new file with mode: 0644]
pipelines/images/imagebuild.sh [new file with mode: 0644]

diff --git a/automation/images/.gitkeep b/automation/images/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/jobs/images.yml b/jobs/images.yml
new file mode 100644 (file)
index 0000000..552cdb4
--- /dev/null
@@ -0,0 +1,258 @@
+---
+## Anchors
+- _images_parameters_default: &images_parameters_imagebuilder_defaults
+    name: 'images_parameters_imagebuilder_defaults'
+    parameters:
+      - string: &images_parameters_OS
+          name: 'OS'
+          description: 'OS name'
+          default: 'debian'
+          required: true
+      - string: &images_parameters_RELEASE
+          name: 'RELEASE'
+          description: 'OS release number or name'
+          default: 'bookworm'
+          required: true
+      - choice: &images_parameters_ARCH
+          name: 'ARCH'
+          description: 'Target architecture'
+          choices:
+            - amd64
+            - i386
+            - arm64
+            - armhf
+            - ppc64el
+            - s390x
+            - riscv64
+      - string: &images_parameters_VARIANT
+          name: 'VARIANT'
+          default: 'cloud'
+          description: 'The base image variant to build off of'
+          required: true
+      - choice: &images_parameters_IMAGE_TYPE
+          name: 'IMAGE_TYPE'
+          choices:
+            - 'lxd'
+            - 'vm'
+          description: 'The type of image to create'
+      - choice:
+          name: 'PROFILE'
+          choices:
+            - 'ci-node'
+            - 'developer'
+          description: 'The ansible group to apply to the image'
+          required: true
+      - string: &images_parameters_LXD_HOST
+          name: 'LXD_HOST'
+          default: 'ci-host-amd64-1a.internal.efficios.com'
+          description: 'The address of the LXD cluster to publish to'
+          required: true
+      - string: &images_parameters_LXD_INSTANCE_PROFILE
+          name: 'LXD_INSTANCE_PROFILE'
+          default: 'ci-rootnode'
+          description: 'The LXD instance profile to use for temporary instances when building images'
+          required: true
+      - string: &images_parameters_GIT_URL
+          name: 'GIT_URL'
+          default: 'https://github.com/lttng/lttng-ci.git'
+          description: 'The source of the repo containing the ansible playbooks'
+          required: true
+      - string: &images_parameters_GIT_BRANCH
+          name: 'GIT_BRANCH'
+          default: 'master'
+          description: 'The branch or commit of the ansible playbook repo to checkout'
+          required: true
+      - bool: &images_parameters_TEST
+          name: 'TEST'
+          default: true
+          description: 'Enable to launch a container of the published image as a test'
+
+- _images_parameters_default: &images_parameters_distrobuilder_defaults
+    name: 'images_parameters_distrobuilder_defaults'
+    parameters:
+      - string:
+          <<: *images_parameters_OS
+      - string:
+          <<: *images_parameters_RELEASE
+      - choice:
+          <<: *images_parameters_ARCH
+      - string:
+          <<: *images_parameters_VARIANT
+      - choice:
+          <<: *images_parameters_IMAGE_TYPE
+      - string:
+          <<: *images_parameters_LXD_HOST
+      - string:
+          <<: *images_parameters_LXD_INSTANCE_PROFILE
+      - string:
+          <<: *images_parameters_GIT_URL
+      - string:
+          <<: *images_parameters_GIT_BRANCH
+      - bool:
+          <<: *images_parameters_TEST
+      - string:
+          name: 'DISTROBUILDER_GIT_URL'
+          default: 'https://github.com/lxc/distrobuilder.git'
+      - string:
+          name: 'DISTROBUILDER_GIT_BRANCH'
+          default: 'main'
+      - string:
+          name: 'LXC_CI_GIT_URL'
+          default: 'https://github.com/lxc/lxc-ci.git'
+      - string:
+          name: 'LXC_CI_GIT_BRANCH'
+          default: 'main'
+      - string:
+          name: 'GO_VERSION'
+          default: '1.21.3'
+
+- _images_properties_defaults: &images_properties_defaults
+    name: 'images_properties_defaults'
+    properties:
+      - build-discarder:
+          num-to-keep: 20
+      - throttle:
+          option: project
+          max-total: 4
+          matrix-builds: false
+
+- _images_parameters_debian_defaults: &images_parameters_debian_defaults
+    name: 'image_parameters_debian_defaults'
+    parameters:
+      - bool:
+          name: 'SKIP_BASE_IMAGES'
+          default: false
+      - bool:
+          name: 'SKIP_PROFILE_IMAGES'
+          default: false
+      - choice: &images_parameters_arch_filter
+          name: 'ARCH_FILTER'
+          choices:
+            - all
+            - amd64
+            - i386
+            - arm64
+            - armhf
+            - ppc64el
+            - riscv64
+            - s390x
+      - choice: &images_parameters_image_type_filter
+          name: 'IMAGE_TYPE_FILTER'
+          choices:
+            - all
+            - lxd
+            - vm
+      - choice: &images_parameters_profile_filter
+          name: 'PROFILE_FILTER'
+          choices:
+            - all
+            - ci-node
+            - developer
+      - choice:
+          name: 'RELEASE_FILTER'
+          choices:
+            - all
+            - bullseye
+            - bookworm
+            - trixie
+            - sid
+      - string:
+          <<: *images_parameters_GIT_URL
+      - string:
+          <<: *images_parameters_GIT_BRANCH
+
+## Defaults
+- defaults:
+    name: imagebuilder
+    concurrent: true
+    description: |
+      <p>Job is managed by Jenkins Job Builder</p>
+    project-type: freestyle
+    publishers:
+      - workspace-cleanup
+    wrappers:
+      - workspace-cleanup
+      - timestamps
+      - ansicolor
+      - credentials-binding:
+          - ssh-user-private-key:
+              credential-id: 'f3c907b6-7485-49e1-afe1-4df24fac4ca1'
+              key-file-variable: SSH_PRIVATE_KEY
+              username-variable: SSH_USERNAME
+              passphrase-variable: SSH_PASSWORD
+          - file:
+              credential-id: 'f3f08275-59ef-42ff-9de5-9beafc7435b8'
+              variable: LXD_CLIENT_CERT
+          - file:
+              credential-id: '0debf23b-191b-4cdf-8a25-04e9a7092a67'
+              variable: LXD_CLIENT_KEY
+      - inject: {}
+
+## Templates
+- job-template:
+    name: images_imagebuilder_{OS}
+    defaults: imagebuilder
+    description: |
+      This pipeline starts distrobuilder and imagebuilder jobs for {OS}
+
+      <p>Job is managed by Jenkins Job Builder</p>
+    project-type: pipeline
+    <<: *images_parameters_debian_defaults
+    IMAGE_TYPES:
+      - lxd
+      - vm
+    PROFILES:
+      - ci-node
+      - developer
+    dsl: !include-jinja2: pipelines/images/default.groovy
+
+- job-template:
+    name: images_distrobuilder
+    defaults: imagebuilder
+    node: 'deb12-amd64-rootnode'
+    <<: *images_parameters_distrobuilder_defaults
+    <<: *images_properties_defaults
+    builders:
+      - shell: !include-raw-escape: pipelines/images/distrobuild.sh
+
+- job-template:
+    name: images_imagebuilder
+    defaults: imagebuilder
+    node: 'deb12-amd64-rootnode'
+    <<: *images_parameters_imagebuilder_defaults
+    <<: *images_properties_defaults
+    builders:
+       - shell: !include-raw-escape: pipelines/images/imagebuild.sh
+
+
+## Views
+- view-template:
+    name: 'Images'
+    view-type: list
+    regex: 'image.*'
+
+## Projects
+
+- project:
+    name: images_imagebuilder_OS
+    OS:
+      - debian
+    ARCHES:
+      - i386
+      - amd64
+    RELEASES:
+      - bullseye
+      - bookworm
+      - trixie
+      - sid
+    jobs:
+      - 'images_imagebuilder_{OS}'
+- project:
+    name: images_basejobs
+    jobs:
+      - 'images_imagebuilder'
+      - 'images_distrobuilder'
+- project:
+    name: images_imagebuilder_views
+    views:
+      - Images
diff --git a/pipelines/images/default.groovy b/pipelines/images/default.groovy
new file mode 100644 (file)
index 0000000..615c48f
--- /dev/null
@@ -0,0 +1,88 @@
+#!groovy
+
+def OS = '{{OS}}'
+def RELEASES = {{RELEASES}}
+def ARCHES = {{ARCHES}}
+def IMAGE_TYPES = {{IMAGE_TYPES}}
+def PROFILES = {{PROFILES}}
+def c = [RELEASES,
+         ARCHES,
+         IMAGE_TYPES].combinations()
+c.removeAll({
+    (params.ARCH_FILTER != 'all' && it[1] != params.ARCH_FILTER) ||
+        (params.IMAGE_TYPE_FILTER != 'all' && it[2] != params.IMAGE_TYPE_FILTER) ||
+        (params.RELEASE_FILTER != 'all' && it[0] != params.RELEASE_FILTER)
+})
+
+// Skip i386 Vms
+c.removeAll({
+    it[1] == 'i386' && it[2] == 'vm'
+})
+
+def base_image_tasks = [:]
+def profile_image_tasks = [:]
+for(int index = 0; index < c.size(); index++) {
+    def envMap = [
+        RELEASE: c[index][0],
+        ARCH: c[index][1],
+        IMAGE_TYPE: c[index][2]
+    ]
+    def image_name = "${OS}/${envMap.RELEASE}/${envMap.ARCH}/${envMap.IMAGE_TYPE}"
+    base_image_tasks[image_name] = { ->
+        def job_ids = []
+        stage("base:${image_name}") {
+            print(envMap)
+            build(
+                job: 'images_distrobuilder',
+                parameters: [
+                    string(name: 'OS', value: OS),
+                    string(name: 'RELEASE', value: envMap.RELEASE),
+                    string(name: 'ARCH', value: envMap.ARCH),
+                    string(name: 'IMAGE_TYPE', value: envMap.IMAGE_TYPE),
+                    string(name: 'GIT_URL', value: params.GIT_URL),
+                    string(name: 'GIT_BRANCH', value: params.GIT_BRANCH)
+                ]
+            )
+        }
+    }
+    for (int profile_index = 0; profile_index < PROFILES.size(); profile_index++) {
+        // Using a second map gets around some weirdness with the closures finding
+        // PROFILES[profile_index] where most jobs would have a null value for the
+        // profile
+        def envMap2 = envMap.clone()
+        envMap2.PROFILE = PROFILES[profile_index]
+        if (env.PROFILE_FILTER == 'all' || env.PROFILE_FILTER == PROFILES[profile_index]) {
+            profile_image_tasks["${PROFILES[profile_index]}:${image_name}"] = { ->
+                print(envMap2)
+                build(
+                    job: 'images_imagebuilder',
+                    parameters: [
+                        string(name: 'OS', value: OS),
+                        string(name: 'RELEASE', value: envMap2.RELEASE),
+                        string(name: 'ARCH', value: envMap2.ARCH),
+                        string(name: 'IMAGE_TYPE', value: envMap2.IMAGE_TYPE),
+                        string(name: 'PROFILE', value:  envMap2.PROFILE),
+                        string(name: 'GIT_URL', value: params.GIT_URL),
+                        string(name: 'GIT_BRANCH', value: params.GIT_BRANCH)
+                    ]
+                )
+            }
+        }
+    }
+}
+
+if (!params.SKIP_BASE_IMAGES) {
+    stage("base images") {
+        parallel(base_image_tasks)
+    }
+}
+
+if (!params.SKIP_PROFILE_IMAGES) {
+    // While it's possible to have the tasks in "base images" start
+    // their respective profile images_imagebuilder steps, it ends
+    // up creating a pipeline overview and log that is difficult to
+    // read in the Jenkins interface.
+    stage("profile images") {
+        parallel(profile_image_tasks)
+    }
+}
diff --git a/pipelines/images/distrobuild.sh b/pipelines/images/distrobuild.sh
new file mode 100644 (file)
index 0000000..b5e624f
--- /dev/null
@@ -0,0 +1,205 @@
+#!/usr/bin/bash -eux
+
+CLEANUP=()
+
+function cleanup {
+    set +e
+    for (( index=${#CLEANUP[@]}-1 ; index >= 0 ; index-- )) ;do
+        ${CLEANUP[$index]}
+    done
+    CLEANUP=()
+    set -e
+}
+
+function fail {
+    CODE="${1:-1}"
+    REASON="${2:-Unknown reason}"
+    cleanup
+    echo "${REASON}" >&2
+    exit "${CODE}"
+}
+
+trap cleanup EXIT TERM INT
+
+env
+
+REQUIRED_VARIABLES=(
+    OS
+    RELEASE
+    ARCH
+    IMAGE_TYPE
+    VARIANT
+    GIT_BRANCH
+    GIT_URL
+    LXD_CLIENT_CERT
+    LXD_CLIENT_KEY
+    TEST
+    DISTROBUILDER_GIT_URL
+    DISTROBUILDER_GIT_BRANCH
+    LXC_CI_GIT_URL
+    LXC_CI_GIT_BRANCH
+    GO_VERSION
+)
+MISSING_VARS=0
+for var in "${REQUIRED_VARIABLES[@]}" ; do
+    if [ ! -v "$var" ] ; then
+        MISSING_VARS=1
+        echo "Missing required variable: '${var}'" >&2
+    fi
+done
+if [[ ! "${MISSING_VARS}" == "0" ]] ; then
+    fail 1 "Missing required variables"
+fi
+
+# Optional variables
+INSTANCE_START_TIMEOUT="${INSTANCE_START_TIMEOUT:-30}"
+VM_ARG=()
+
+# Install lxd-client
+apt-get update
+apt-get install -y lxd-client
+mkdir -p ~/.config/lxc
+cp "${LXD_CLIENT_CERT}" ~/.config/lxc/client.crt
+cp "${LXD_CLIENT_KEY}" ~/.config/lxc/client.key
+CLEANUP+=(
+    "rm -f ${HOME}/.config/lxc/client.crt"
+    "rm -f ${HOME}/.config/lxc/client.key"
+)
+lxc remote add ci --accept-certificate --auth-type tls "${LXD_HOST}"
+lxc remote switch ci
+
+# Exit gracefully if the lxc images: provides the base image
+IMAGE_NAME="${OS}/${RELEASE}/${VARIANT}/${ARCH}"
+TYPE_FILTER='type=container'
+if [[ "${IMAGE_TYPE}" == "vm" ]] ; then
+    TYPE_FILTER='type=virtual-machine'
+fi
+if [[ "$(lxc image list -f csv images:"${IMAGE_NAME}" -- "${TYPE_FILTER}" | wc -l)" != "0" ]] ; then
+    echo "Image '${IMAGE_NAME}' provided by 'images:' remote"
+    exit 0
+fi
+
+# Get go
+apt-get install -y wget
+wget "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -O - | tar -C /usr/local -xzf -
+export PATH="${PATH}:/usr/local/go/bin"
+
+# Install distrobuilder
+apt-get install -y debootstrap rsync gpg squashfs-tools git \
+        btrfs-progs dosfstools qemu-utils gdisk
+cd "${WORKSPACE}"
+git clone --branch="${DISTROBUILDER_GIT_BRANCH}" "${DISTROBUILDER_GIT_URL}" distrobuilder
+cd distrobuilder
+make
+PATH="${PATH}:${HOME}/go/bin"
+
+# Get CI repo
+cd "${WORKSPACE}"
+git clone --branch="${GIT_BRANCH}" "${GIT_URL}" ci
+
+# Get the LXC CI repo
+cd "${WORKSPACE}"
+git clone --branch="${LXC_CI_GIT_BRANCH}" "${LXC_CI_GIT_URL}" lxc-ci
+
+IMAGE_DIRS=(
+    "${WORKSPACE}/ci/automation/images"
+    "${WORKSPACE}/lxc-ci/images"
+)
+EXTENSIONS=(
+    'yml'
+    'yaml'
+)
+IMAGE_FILE=''
+for IMAGE_DIR in "${IMAGE_DIRS[@]}" ; do
+    for EXTENSION in "${EXTENSIONS[@]}" ; do
+        if [ -f "${IMAGE_DIR}/${OS}.${EXTENSION}" ] ; then
+            IMAGE_FILE="${IMAGE_DIR}/${OS}.${EXTENSION}"
+            break 2;
+        fi
+    done
+done
+
+if [[ "${IMAGE_FILE}" == "" ]] ; then
+    fail 1 "Unable to find image file for '${OS}' in ${IMAGE_DIRS[@]}"
+fi
+
+DISTROBUILDER_ARGS=(
+    distrobuilder
+    build-incus
+)
+if [[ "${IMAGE_TYPE}" == "vm" ]] ; then
+    DISTROBUILDER_ARGS+=('--vm')
+    VM_ARG=('--vm')
+fi
+
+# This could be quite large, and /tmp may be a tmpfs backed
+# by memory, so instead make it relative to the workspace directory
+BUILD_DIR=$(mktemp -d -p "${WORKSPACE}")
+CLEANUP+=(
+    "rm -rf ${BUILD_DIR}"
+)
+DISTROBUILDER_ARGS+=(
+    "${IMAGE_FILE}"
+    "${BUILD_DIR}"
+    '-o'
+    "image.architecture=${ARCH}"
+    '-o'
+    "image.variant=${VARIANT}"
+    '-o'
+    "image.release=${RELEASE}"
+    '-o'
+    "image.serial=$(date -u +%Y%m%dT%H:%M:%S%z)"
+)
+
+# Run the build
+${DISTROBUILDER_ARGS[@]}
+
+# Import
+# As 'distrobuilder --import-into-incus=alias' doesn't work since it only
+# connects to the local unix socket, and the remote instance cannot be specified
+# at this time.
+ROOTFS="${BUILD_DIR}/rootfs.squashfs"
+if [[ "${IMAGE_TYPE}" == "vm" ]] ; then
+    ROOTFS="${BUILD_DIR}/disk.qcow2"
+fi
+
+# Work-around for lxd not using qemu-system-i386: set the architecture to x86_64
+# which will use qemu-system-x86_64 and still run 32bit userspace/kernels fine.
+if [[ "${ARCH}" == "i386" ]] ; then
+    TMP_DIR=$(mktemp -d)
+    pushd "${TMP_DIR}"
+    tar -xf "${BUILD_DIR}/incus.tar.xz"
+    sed -i 's/architecture: i386/architecture: x86_64/' metadata.yaml
+    tar -cf "${BUILD_DIR}/incus.tar.xz" ./*
+    popd
+    rm -rf "${TMP_DIR}"
+fi
+
+lxc image import "${BUILD_DIR}/incus.tar.xz" "${ROOTFS}" --alias="${IMAGE_NAME}" ci:
+
+if [[ "${TEST}" == "true" ]] ; then
+    set +e
+    INSTANCE_NAME=''
+    if INSTANCE_NAME="$(lxc -q launch -e ${VM_ARG[@]} -p default -p "${LXD_INSTANCE_PROFILE}" "${IMAGE_NAME}")" ; then
+        INSTANCE_NAME="$(echo "${INSTANCE_NAME}" | cut -d':' -f2 | tr -d ' ')"
+        CLEANUP+=(
+            "lxc stop ${INSTANCE_NAME}"
+        )
+    else
+        fail 1 "Failed to launch instance using image '${IMAGE_NAME}'"
+    fi
+    TIME_REMAINING="${INSTANCE_START_TIMEOUT}"
+    INSTANCE_STATUS=''
+    while true ; do
+        INSTANCE_STATUS="$(lxc exec "${INSTANCE_NAME}" hostname)"
+        if [[ "${INSTANCE_STATUS}" == "${INSTANCE_NAME}" ]] ; then
+            break
+        fi
+        sleep 1
+        TIME_REMAINING=$((TIME_REMAINING - 1))
+        if [ "${TIME_REMAINING}" -lt "0" ] ; then
+            fail 1 "Timed out waiting for instance to become available via 'lxc exec'"
+        fi
+    done
+    set -e
+fi
diff --git a/pipelines/images/imagebuild.sh b/pipelines/images/imagebuild.sh
new file mode 100644 (file)
index 0000000..340f71c
--- /dev/null
@@ -0,0 +1,223 @@
+#!/usr/bin/bash -eux
+
+CLEANUP=()
+
+function cleanup {
+    set +e
+    for (( index=${#CLEANUP[@]}-1 ; index >= 0 ; index-- )) ;do
+        ${CLEANUP[$index]}
+    done
+    CLEANUP=()
+    set -e
+}
+
+function fail {
+    CODE="${1:-1}"
+    REASON="${2:-Unknown reason}"
+    cleanup
+    echo "${REASON}" >&2
+    exit "${CODE}"
+}
+
+trap cleanup EXIT TERM INT
+
+env
+
+REQUIRED_VARIABLES=(
+    OS              # OS name
+    RELEASE         # OS release
+    ARCH            # The image architecture
+    IMAGE_TYPE      # The image type to create
+    VARIANT         # The variant of the base image to use
+    PROFILE         # The ansible group to apply to the new image
+    GIT_BRANCH      # The git branch of the automation repo to checkout
+    GIT_URL         # The git URL of the automation repo to checkout
+    LXD_CLIENT_CERT # Path to LXD client certificate
+    LXD_CLIENT_KEY  # Path to LXD client certificate key
+    SSH_PRIVATE_KEY # Path to SSH private key
+    TEST            # 'true' to test launching published image
+)
+MISSING_VARS=0
+for var in "${REQUIRED_VARIABLES[@]}" ; do
+    if [ ! -v "$var" ] ; then
+        MISSING_VARS=1
+        echo "Missing required variable: '${var}'" >&2
+    fi
+done
+if [[ ! "${MISSING_VARS}" == "0" ]] ; then
+    fail 1 "Missing required variables"
+fi
+
+# Default optional variables
+INSTANCE_START_TIMEOUT="${INSTANCE_START_TIMEOUT:-30}"
+NETWORK_SLEEP="${NETWORK_SLEEP:-15}"
+
+# Dependencies
+apt-get -y install lxd-client ansible jq
+
+# Configuration
+mkdir -p ~/.config/lxc
+cp "${LXD_CLIENT_CERT}" ~/.config/lxc/client.crt
+cp "${LXD_CLIENT_KEY}" ~/.config/lxc/client.key
+CLEANUP+=(
+    "rm -f ${HOME}/.config/lxc/client.crt"
+    "rm -f ${HOME}/.config/lxc/client.key"
+)
+lxc remote add ci --accept-certificate --auth-type tls "${LXD_HOST}"
+lxc remote switch ci
+
+# Clone lttng-ci
+git clone -b "${GIT_BRANCH}" "${GIT_URL}" ci
+cd ci/automation/ansible || exit 1
+
+SOURCE_IMAGE_NAME="${OS}/${RELEASE}/${VARIANT}/${ARCH}"
+# Include IMAGE_TYPE since an alias may only be defined once even if the
+# type of the image differs
+TARGET_IMAGE_NAME="${OS}/${RELEASE}/${VARIANT}/${ARCH}/${PROFILE}/${IMAGE_TYPE}"
+INSTANCE_NAME=''
+# Try from local cache
+VM_ARG=()
+if [ "${IMAGE_TYPE}" == "vm" ] ; then
+    VM_ARG=("--vm")
+fi
+
+set +e
+# Test
+# It's possible that concurrent image creation when running parallel jobs causes
+# an error during the launch:
+#   Error: Failed instance creation: UNIQUE constraint failed: images.project_id, images.fingerprint
+# C.f. https://github.com/canonical/lxd/issues/11636
+#
+TRIES_MAX=3
+TRIES=0
+while [[ "${TRIES}" -lt "${TRIES_MAX}" ]] ; do
+    if ! INSTANCE_NAME=$(lxc -q launch -e "${VM_ARG[@]}" -p default -p "${LXD_INSTANCE_PROFILE}" "${SOURCE_IMAGE_NAME}") ; then
+        # Try from images
+        if ! INSTANCE_NAME=$(lxc -q launch -e "${VM_ARG[@]}" -p default -p "${LXD_INSTANCE_PROFILE}" images:"${SOURCE_IMAGE_NAME}") ; then
+            TRIES=$((TRIES + 1))
+            echo "Failed to deployed ephemereal instance attempt ${TRIES}/${TRIES_MAX}"
+            if [[ "${TRIES}" -lt  "${TRIES_MAX}" ]] ; then
+                continue
+            fi
+            fail 1 "Failed to deploy ephemereal instance"
+        else
+            break
+        fi
+    else
+        break
+    fi
+done
+INSTANCE_NAME="$(echo "${INSTANCE_NAME}" | cut -d ':' -f 2 | tr -d ' ')"
+set -e
+
+CLEANUP+=(
+    "lxc stop ${INSTANCE_NAME}"
+)
+
+# VMs may take more time to start, wait until instance is running
+TIME_REMAINING="${INSTANCE_START_TIMEOUT}"
+while true ; do
+    set +e
+    INSTANCE_STATUS=$(lxc exec "${INSTANCE_NAME}" hostname)
+    set -e
+    if [[ "${INSTANCE_STATUS}" == "${INSTANCE_NAME}" ]] ; then
+        break
+    fi
+    sleep 1
+    TIME_REMAINING=$((TIME_REMAINING - 1))
+    if [ "${TIME_REMAINING}" -lt "0" ] ; then
+        fail 1 "Timed out waiting for instance to become available via 'lxc exec'"
+    fi
+done
+
+# Wait for cloud-init to finish
+if [[ "${VARIANT}" == "cloud" ]] ; then
+    lxc exec "${INSTANCE_NAME}" -- cloud-init status -w
+fi
+
+# Wait for instance to have an ip address (@TODO: is there a better approach?)
+sleep "${NETWORK_SLEEP}"
+
+# @TODO: Handle case when iputils2 is not installed
+INSTANCE_IP=''
+POTENTIAL_INTERFACES=(eth0 enp5s0)
+lxc exec "${INSTANCE_NAME}" -- ip a
+set +e
+for interface in "${POTENTIAL_INTERFACES[@]}" ; do
+    if ! DEV_INFO="$(lxc exec "${INSTANCE_NAME}" -- ip a show dev "${interface}")" ; then
+        continue
+    fi
+    INSTANCE_IP="$(echo "${DEV_INFO}" | grep -Eo 'inet [^ ]* ' | cut -d' ' -f2 | cut -d'/' -f1)"
+    if [[ "${INSTANCE_IP}" != "" ]] ; then
+        break
+    fi
+done
+set -e
+if [[ "${INSTANCE_IP}" == "" ]] ; then
+    fail 1 "Failed to determine instance IP address"
+fi
+
+ssh-keyscan "${INSTANCE_IP}" >> ~/.ssh/known_hosts2
+#lxc exec "${INSTANCE_NAME}" -- bash -c 'for i in /etc/ssh/ssh_host_*_key ; do ssh-keygen -l -f "$i" ; done' >> "${HOME}/.ssh/known_hosts"
+CLEANUP+=(
+    "rm -f ${HOME}/.ssh/known_hosts2"
+)
+cp "${SSH_PRIVATE_KEY}" ~/.ssh/id_rsa
+ssh-keygen -f ~/.ssh/id_rsa -y > ~/.ssh/id_rsa.pub
+CLEANUP+=(
+    "rm -f ${HOME}/.ssh/id_rsa.pub"
+    "rm -f ${HOME}/.ssh/id_rsa"
+)
+lxc file push ~/.ssh/id_rsa.pub "ci:${INSTANCE_NAME}/root/.ssh/authorized_keys2"
+
+# Confirm working SSH connection
+if ! ssh "${INSTANCE_IP}" hostname ; then
+    fail 1 "Unable to reach ephemereal instance over SSH"
+fi
+
+# Run playbook
+cat > fake-inventory <<EOF
+[${PROFILE/-/_}]
+${INSTANCE_IP}
+EOF
+CLEANUP+=(
+    "rm -f $(pwd)/fake-inventory"
+)
+
+LANG=C ANSIBLE_STRATEGY=linear ansible-playbook site.yml \
+    -e '{"compilers_legacy_install": false, "jenkins_user": false, "lttng_modules_checkout_repo": false}' \
+    -l "${INSTANCE_IP}" -i fake-inventory
+
+# Cleanup instance side
+# @TODO: Distro switch for apt/dnf/yum/etc.
+lxc exec "${INSTANCE_NAME}" -- apt-get clean
+lxc exec "${INSTANCE_NAME}" -- rm -rf /root/.ssh/authorized_keys2
+lxc exec "${INSTANCE_NAME}" -- cloud-init clean
+lxc exec "${INSTANCE_NAME}" -- bash -c 'history -cw'
+
+# Publish
+lxc publish "${INSTANCE_NAME}" --alias "${TARGET_IMAGE_NAME}" -f
+
+TRIES=0
+
+if [[ "${TEST}" == "true" ]] ; then
+    set +e
+    while [[ "${TRIES}" -lt "${TRIES_MAX}" ]] ; do
+        if ! INSTANCE_NAME=$(lxc -q launch -e "${VM_ARG[@]}" -p default -p "${LXD_INSTANCE_PROFILE}" "${TARGET_IMAGE_NAME}")  ; then
+            TRIES=$((TRIES + 1))
+            echo "Failed to launch instance try ${TRIES}/${TRIES_MAX}"
+            if [[ "${TRIES}" -lt "${TRIES_MAX}" ]] ; then
+                sleep $((1 + RANDOM % 10))
+                continue
+            fi
+            fail 1 "Failed to launch an instance using newly published image '${TARGET_IMAGE_NAME}'"
+        else
+            INSTANCE_NAME="$(echo "${INSTANCE_NAME}" | cut -d':' -f2 | tr -d ' ')"
+            CLEANUP+=(
+                "lxc stop -f ${INSTANCE_NAME}"
+            )
+            break
+        fi
+    done
+    set -e
+fi
This page took 0.033527 seconds and 4 git commands to generate.