patch: Update actions/checkout digest to 8e8c483 #137
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| # https://github.com/balena-os/balena-yocto-scripts/blob/master/.github/workflows/yocto-build-deploy.yml | |
| # https://github.com/balena-os/balena-yocto-scripts/blob/master/automation/entry_scripts/balena-generate-ami.sh | |
| name: Register balenaOS AWS/EC2 AMI | |
| on: | |
| pull_request: | |
| branches: | |
| - master | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| id-token: write # AWS GitHub OIDC required: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.number || github.ref }} | |
| # cancel jobs in progress for updated PRs, but not merge or tag events | |
| cancel-in-progress: ${{ github.event.action == 'synchronize' }} | |
| env: | |
| AWS_DEFAULT_REGION: ${{ vars.AWS_REGION || 'us-east-1' }} | |
| AWS_IAM_ROLE: ${{ vars.AWS_IAM_ROLE }} | |
| AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }} | |
| AWS_SECURITY_GROUP_ID: ${{ vars.AWS_SECURITY_GROUP_ID }} | |
| AWS_SUBNET_IDS: ${{ vars.AWS_SUBNET_IDS }} | |
| BALENA_TEST_ORG: ${{ vars.BALENA_TEST_ORG || 'belodetek' }} | |
| VMIMPORT_S3_BUCKET: ${{ vars.VMIMPORT_S3_BUCKET }} | |
| jobs: | |
| import-vm: | |
| defaults: | |
| run: | |
| shell: bash --noprofile --norc -eo pipefail -x {0} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - generic-amd64 | |
| - generic-aarch64 | |
| include: | |
| - target: generic-amd64 | |
| # renovate: datasource=github-releases depName=balena-os/balena-generic | |
| BALENA_OS_VERSION: v6.8.0 | |
| balena_app_name: ${{ vars.BALENA_APP_NAME_AMD64 || 'bh.cr/balena_os/cloud-config-amd64' }} | |
| balena_app_commit: ${{ vars.BALENA_APP_COMMIT_AMD64 || '461031537d2655560b9bc29ea2e1405c' }} | |
| development_mode: true | |
| ec2_instance_type: c7a.medium | |
| aws_ec2_metadata_v2: optional # IMDSv1 | |
| - target: generic-aarch64 | |
| # renovate: datasource=github-releases depName=balena-os/balena-generic | |
| BALENA_OS_VERSION: v6.8.0 | |
| balena_app_name: ${{ vars.BALENA_APP_NAME_AARCH64 || 'bh.cr/balena_os/cloud-config-aarch64' }} | |
| balena_app_commit: ${{ vars.BALENA_APP_COMMIT_AARCH64 || 'cfe1502b178daf06475c939e16473b9c' }} | |
| development_mode: true | |
| ec2_instance_type: m8g.medium | |
| aws_ec2_metadata_v2: required # IMDSv2 | |
| steps: | |
| # https://github.com/unfor19/install-aws-cli-action | |
| - uses: unfor19/install-aws-cli-action@f5b46b7f32cf5e7ebd652656c5036bf83dd1e60c # v1 | |
| - uses: aws-actions/configure-aws-credentials@259eac73d3898af00550394d11116c4a94c9b1dc | |
| with: | |
| aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} | |
| role-session-name: github-${{ github.job }}-${{ github.run_id }}-${{ github.run_attempt }} | |
| # balena-io/environments-bases: aws/balenacloud/ephemeral-tests/balena-tests-iam.yml | |
| role-to-assume: ${{ vars.AWS_IAM_ROLE }} | |
| mask-aws-account-id: false | |
| - name: Set AWS/EC2/AMI image architecture string from balena device type | |
| id: ami | |
| run: | | |
| if [[ '${{ matrix.target }}' =~ "amd64" ]]; then | |
| echo 'arch=x86_64' >>"${GITHUB_OUTPUT}" | |
| elif [[ '${{ matrix.target }}' =~ "aarch64" ]]; then | |
| echo 'arch=arm64' >>"${GITHUB_OUTPUT}" | |
| else | |
| exit 1 | |
| fi | |
| echo "name=balena-os-${{ matrix.BALENA_OS_VERSION }}-${{ matrix.target }}" \ | |
| | sed 's/+/-/g' >>"${GITHUB_OUTPUT}" | |
| - name: Check if the AWS/EC2 AMI already exists for this version of balenaOS | |
| id: check | |
| env: | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 1 | |
| existing_image_id=$(aws ec2 describe-images --output text \ | |
| --filters "Name=name,Values=${AMI_NAME}" \ | |
| --query 'Images[*].[ImageId]') | |
| if [ -n "$existing_image_id" ]; then | |
| echo 'skip=true' >>"${GITHUB_OUTPUT}" | |
| fi | |
| # https://github.com/balena-io-examples/setup-balena-action | |
| - uses: balena-io-examples/setup-balena-action@7bd1b61bd4165bac817cf0ba19ed53a25e8c66ca # v0.0.61 | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| # renovate: datasource=github-releases depName=balena-io/balena-cli | |
| BALENA_CLI_VERSION: v23.2.0 | |
| with: | |
| cli-version: ${{ env.BALENA_CLI_VERSION }} | |
| balena-token: ${{ secrets.BALENA_API_KEY }} | |
| - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| id: cache | |
| if: steps.check.outputs.skip != 'true' | |
| continue-on-error: true | |
| with: | |
| key: ${{ runner.os }}-balena-os-${{ matrix.target }}-image-${{ matrix.BALENA_OS_VERSION }} | |
| path: balena.img | |
| - name: download balena.img | |
| id: download | |
| if: steps.cache.outputs.cache-hit != 'true' && steps.check.outputs.skip != 'true' | |
| run: | | |
| balena os download ${{ matrix.target }} -o balena.img --version ${{ matrix.BALENA_OS_VERSION }} | |
| - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| if: always() && steps.download.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| with: | |
| path: balena.img | |
| key: ${{ runner.os }}-balena-os-${{ matrix.target }}-image-${{ matrix.BALENA_OS_VERSION }} | |
| - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| id: preloaded | |
| if: steps.check.outputs.skip != 'true' | |
| continue-on-error: true | |
| with: | |
| key: ${{ runner.os }}-balena-os-${{ matrix.target }}-preload-${{ matrix.BALENA_OS_VERSION }}-${{ matrix.balena_app_commit }} | |
| path: balena.img | |
| - name: Create temporary fleet to preload the public app | |
| id: preload-fleet | |
| if: steps.preloaded.outputs.cache-hit != 'true' && steps.check.outputs.skip != 'true' | |
| run: | | |
| user="$(balena whoami | grep USERNAME | awk '{print $2}')" | |
| tmpfleet="tmpload-$(( RANDOM ))" | |
| balena fleet create "${tmpfleet}" --type ${{ matrix.target }} --organization "${user}" | |
| docker pull ${{ matrix.balena_app_name }}/${{ matrix.balena_app_commit }} | |
| tmpush=$(mktemp -d) | |
| pushd "${tmpush}" | |
| cat <<EOF> Dockerfile | |
| FROM ${{ matrix.balena_app_name }}/${{ matrix.balena_app_commit }} | |
| EOF | |
| balena push "${tmpfleet}" | |
| popd | |
| rm -rf "${tmpush}" | |
| echo "name=${user}/${tmpfleet}" >>"${GITHUB_OUTPUT}" | |
| - name: Pre-load cloud-config app to handle cloud provider metadata interfaces | |
| id: preload | |
| if: steps.preloaded.outputs.cache-hit != 'true' && steps.check.outputs.skip != 'true' | |
| env: | |
| PRELOAD_FLEET: ${{ steps.preload-fleet.outputs.name }} | |
| run: | | |
| balena preload --debug --pin-device-to-release balena.img \ | |
| --fleet "${PRELOAD_FLEET}" \ | |
| --commit current | |
| - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| if: always() && steps.preload.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| with: | |
| path: balena.img | |
| key: ${{ runner.os }}-balena-os-${{ matrix.target }}-preload-${{ matrix.BALENA_OS_VERSION }}-${{ matrix.balena_app_commit }} | |
| - name: Copy balenaOS image to AWS/S3 for import | |
| id: s3 | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| VMIMPORT_S3_BUCKET: ${{ env.VMIMPORT_S3_BUCKET }} | |
| run: | | |
| s3_key="balena-${RANDOM}.tmp" | |
| s3_url="s3://${VMIMPORT_S3_BUCKET}/preloaded-images/${s3_key}" | |
| aws s3 cp --no-progress balena.img "${s3_url}" | |
| echo "key=${s3_key}" >>"${GITHUB_OUTPUT}" | |
| echo "url=${s3_url}" >>"${GITHUB_OUTPUT}" | |
| - name: Create AWS/EC2 EBS snapshot from balenaOS image | |
| id: snapshot | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| VMIMPORT_S3_BUCKET: ${{ env.VMIMPORT_S3_BUCKET }} | |
| S3_KEY: ${{ steps.s3.outputs.key }} | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 1 | |
| import_task_id=$(aws ec2 import-snapshot \ | |
| --description "snapshot-${AMI_NAME}" \ | |
| --disk-container "Description=balena-os,Format=RAW,UserBucket={S3Bucket=${VMIMPORT_S3_BUCKET},S3Key=preloaded-images/${S3_KEY}}" \ | |
| | jq -r .ImportTaskId) | |
| while true; do | |
| response="$(aws ec2 describe-import-snapshot-tasks --import-task-ids "${import_task_id}")" | |
| status_message="$(echo "${response}" | jq -r ".ImportSnapshotTasks[].SnapshotTaskDetail.StatusMessage")" | |
| progress="$(echo "${response}" | jq -r ".ImportSnapshotTasks[].SnapshotTaskDetail.Progress")" | |
| status="$(echo "${response}" | jq -r ".ImportSnapshotTasks[].SnapshotTaskDetail.Status")" | |
| echo "::info::${status_message}: ${progress}% (${status})" | |
| if [[ "$status" =~ "completed" ]]; then | |
| break | |
| fi | |
| if [[ "$status" =~ "deleting" ]]; then | |
| echo "::error::${status_message}" | |
| exit 1 | |
| fi | |
| sleep $(( (RANDOM % 30) + 30))s | |
| done | |
| snapshot_id=$(aws ec2 describe-import-snapshot-tasks --import-task-ids "${import_task_id}" \ | |
| | jq -r '.ImportSnapshotTasks[].SnapshotTaskDetail.SnapshotId') | |
| echo "id=${snapshot_id}" >>"${GITHUB_OUTPUT}" | |
| - name: Create AWS/EC2 AMI from snapshot | |
| id: image | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| AMI_ARCHITECTURE: ${{ steps.ami.outputs.arch }} | |
| AMI_BOOT_MODE: uefi | |
| AMI_EBS_DELETE_ON_TERMINATION: true | |
| AMI_EBS_VOLUME_SIZE: 8 | |
| AMI_EBS_VOLUME_TYPE: gp3 | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| AMI_ROOT_DEVICE_NAME: /dev/sda1 | |
| AMI_SNAPSHOT_ID: ${{ steps.snapshot.outputs.id }} | |
| BALENA_API_KEY: ${{ secrets.BALENA_API_KEY }} | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 1 | |
| existing_image_id=$(aws ec2 describe-images --output text \ | |
| --filters "Name=name,Values=${AMI_NAME}" \ | |
| --query 'Images[*].[ImageId]') | |
| if [ -n "$existing_image_id" ]; then | |
| exit 1 | |
| fi | |
| # only supported on x86_64 | |
| if [ "${AMI_ARCHITECTURE}" = "x86_64" ]; then | |
| tpm_opts="--tpm-support v2.0" | |
| fi | |
| block_device_mappings="DeviceName=${AMI_ROOT_DEVICE_NAME},Ebs={ | |
| DeleteOnTermination=${AMI_EBS_DELETE_ON_TERMINATION}, | |
| SnapshotId=${AMI_SNAPSHOT_ID}, | |
| VolumeSize=${AMI_EBS_VOLUME_SIZE}, | |
| VolumeType=${AMI_EBS_VOLUME_TYPE} | |
| }" | |
| # shellcheck disable=SC2086 | |
| image_id=$(aws ec2 register-image \ | |
| --name "${AMI_NAME}" \ | |
| --architecture "${AMI_ARCHITECTURE}" \ | |
| --virtualization-type hvm \ | |
| ${tpm_opts} \ | |
| --ena-support \ | |
| --root-device-name "${AMI_ROOT_DEVICE_NAME}" \ | |
| --boot-mode "${AMI_BOOT_MODE}" \ | |
| --block-device-mappings "${block_device_mappings}" | jq -r .ImageId) | |
| [ -z "${image_id}" ] && exit 1 | |
| device_type='${{ matrix.target }}' | |
| balena_os_version="$(printf '${{ matrix.BALENA_OS_VERSION }}' | jq -sRr '@uri')" | |
| changelog_link="$(echo '${{ matrix.BALENA_OS_VERSION }}' | sed 's/\.//g' | sed 's/+//g')" | |
| version_string="$(echo '${{ matrix.BALENA_OS_VERSION }}' | sed 's/^v//' | cut -d'+' -f1)" | |
| semver_build="$(echo '${{ matrix.BALENA_OS_VERSION }}' | sed 's/^v//' | awk -F'+' '{print $2}')" | |
| semver_major=$(echo "${version_string}" | cut -d'.' -f1) | |
| semver_minor=$(echo "${version_string}" | cut -d'.' -f2) | |
| semver_patch=$(echo "${version_string}" | cut -d'.' -f3) | |
| revision="${semver_build/rev//}" | |
| revision="${revision:-0}" | |
| response="$(curl -sf "https://api.balena-cloud.com/v7/application?\$select=is_for__device_type&\$expand=application_tag(\$select=tag_key,value),is_for__device_type(\$select=slug),owns__release(\$select=id,known_issue_list,raw_version,variant,phase;\$expand=release_tag(\$select=tag_key,value),belongs_to__application(\$select=id);\$filter=(semver_major%20eq%20${semver_major})%20and%20(semver_minor%20eq%20${semver_minor})%20and%20(semver_patch%20eq%20${semver_patch})%20and%20(semver_prerelease%20eq%20%27%27)%20and%20(semver_build%20eq%20%27${semver_build}%27)%20and%20(revision%20eq%20${revision})%20and%20(variant%20in%20(%27%27,%20%27prod%27)))&\$filter=(is_host%20eq%20true)%20and%20(is_for__device_type/any(dt:dt/slug%20in%20(%27${device_type}%27)))" \ | |
| -H "authorization: Bearer ${BALENA_API_KEY}")" | |
| hostapp_id="$(echo "${response}" | jq -re .d[].owns__release[].belongs_to__application[].id)" | |
| release_id="$(echo "${response}" | jq -re .d[].owns__release[].id)" | |
| hostapp_release_url="https://dashboard.balena-cloud.com/apps/${hostapp_id}/releases/${release_id}/summary" | |
| changelog_url="https://github.com/balena-os/balena-generic/blob/${balena_os_version}/CHANGELOG.md#${changelog_link}" | |
| aws ec2 create-tags --resources "${image_id}" \ | |
| --tags Key=Name,Value="${AMI_NAME}" \ | |
| Key=publisher,Value="belodetek" \ | |
| Key=email,Value="[email protected]" \ | |
| Key=github,Value="https://github.com/belodetek" \ | |
| Key=github-release,Value="https://github.com/balena-os/balena-generic/releases/tag/${balena_os_version}" \ | |
| Key=github-tag,Value="https://github.com/balena-os/balena-generic/tree/${balena_os_version}" \ | |
| Key=meta-changelog,Value='https://github.com/balena-os/meta-balena/blob/master/CHANGELOG.md' \ | |
| Key=changelog,Value="${changelog_url}" \ | |
| Key=hostapp-release,Value="${hostapp_release_url}" \ | |
| Key=license,Value='https://www.apache.org/licenses/LICENSE-2.0' | |
| echo "id=${image_id}" >>"${GITHUB_OUTPUT}" | |
| - name: generate SSH private key | |
| if: steps.check.outputs.skip != 'true' | |
| id: generate-key-pair | |
| env: | |
| AWS_DEFAULT_REGION: ${{ env.AWS_DEFAULT_REGION }} | |
| run: | | |
| key_name="${{ matrix.target }}-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${GITHUB_RUN_ATTEMPT}" | |
| echo "key_name=${key_name}" >>"${GITHUB_OUTPUT}" | |
| set +x | |
| private_key_material="$(aws ec2 create-key-pair \ | |
| --key-name "${key_name}" | jq -r .KeyMaterial)" | |
| public_key="$(aws ec2 describe-key-pairs --include-public-key \ | |
| --key-name "${key_name}" | jq -re .KeyPairs[].PublicKey)" | |
| # https://stackoverflow.com/a/70384422/1559300 | |
| # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#masking-a-value-in-log | |
| while read -r line; do | |
| echo "::add-mask::${line}" | |
| done <<< "${private_key_material}" | |
| ssh_private_key="$(cat << EOF | |
| ${private_key_material} | |
| EOF | |
| )" | |
| # shellcheck disable=SC2129 | |
| echo "ssh_private_key<<EOF" >>"${GITHUB_OUTPUT}" | |
| { echo "${ssh_private_key}"; echo "EOF"; } >>"${GITHUB_OUTPUT}" | |
| echo "ssh_public_key=${public_key}" >> "${GITHUB_OUTPUT}" | |
| # https://github.com/webfactory/ssh-agent | |
| - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 | |
| if: steps.check.outputs.skip != 'true' | |
| with: | |
| ssh-private-key: ${{ steps.generate-key-pair.outputs.ssh_private_key }} | |
| - name: Crete balenaCloud fleet to test the image | |
| id: test-fleet | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| BALENA_TEST_ORG: ${{ env.BALENA_TEST_ORG }} | |
| SSH_PRIVATE_KEY: ${{ steps.generate-key-pair.outputs.ssh_private_key }} | |
| SSH_PUBLIC_KEY: ${{ steps.generate-key-pair.outputs.ssh_public_key }} | |
| run: | | |
| ami_test_fleet=$(openssl rand -hex 4) | |
| >&2 balena fleet create "${ami_test_fleet}" \ | |
| --organization "${BALENA_TEST_ORG}" \ | |
| --type '${{ matrix.target }}' | |
| echo "${SSH_PRIVATE_KEY}" >"${HOME}/.ssh/id_rsa" | |
| echo "${SSH_PUBLIC_KEY}" >"${HOME}/.ssh/id_rsa.pub" | |
| for key in $(balena ssh-key list | grep -v ID | awk '{print $1}'); do | |
| fp=$(balena ssh-key "${key}" | tail -n 1 | ssh-keygen -E md5 -lf /dev/stdin | awk '{print $2}') | |
| if [[ $fp =~ $(ssh-keygen -E md5 -lf "${HOME}/.ssh/id_rsa" | awk '{print $2}') ]]; then | |
| match="${key}" | |
| break | |
| fi | |
| done | |
| if [[ -z ${match:-} ]]; then | |
| balena ssh-key add "${ami_test_fleet}" "${HOME}/.ssh/id_rsa.pub" | |
| else | |
| balena ssh-key "${match}" | |
| fi | |
| uuid="$(balena device register "${BALENA_TEST_ORG}/${ami_test_fleet}" | awk '{print $4}')" | |
| echo "uuid=${uuid}" >>"${GITHUB_OUTPUT}" | |
| if [ '${{ matrix.development_mode }}' = true ]; then | |
| _dev_mode="--dev"; | |
| else | |
| _dev_mode=""; | |
| fi | |
| config_json=$(mktemp) | |
| echo "config_json=${config_json}" >>"${GITHUB_OUTPUT}" | |
| >&2 balena config generate \ | |
| --network ethernet \ | |
| --version '${{ matrix.BALENA_OS_VERSION }}' \ | |
| --device "${uuid}" \ | |
| --appUpdatePollInterval 5 \ | |
| --output "${config_json}" \ | |
| ${_dev_mode} | |
| if [ ! -f "${config_json}" ]; then | |
| exit 1 | |
| else | |
| new_uuid=$(jq -r '.uuid' "${config_json}") | |
| if [ "${new_uuid}" != "${uuid}" ]; then | |
| exit 1 | |
| fi | |
| fi | |
| tmpconf="$(mktemp)" | |
| cat <"${config_json}" | jq -re --arg pub_key "${SSH_PUBLIC_KEY}" '.os.sshKeys += [$pub_key]' >"${tmpconf}" | |
| cat <"${tmpconf}" | jq -re >"${config_json}" | |
| rm -f "${tmpconf}" | |
| echo "name=${BALENA_TEST_ORG}/${ami_test_fleet}" >>"${GITHUB_OUTPUT}" | |
| - name: Test AWS/EC2 AMI image | |
| id: test | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| AWS_EC2_KEY_NAME: ${{ steps.generate-key-pair.outputs.key_name }} | |
| AWS_EC2_METADATA_V2: ${{ matrix.aws_ec2_metadata_v2 }} | |
| AWS_SECURITY_GROUP_ID: ${{ env.AWS_SECURITY_GROUP_ID }} | |
| AWS_SUBNET_IDS: ${{ env.AWS_SUBNET_IDS }} | |
| CONFIG_JSON: ${{ steps.test-fleet.outputs.config_json }} | |
| UUID: ${{ steps.test-fleet.outputs.uuid }} | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 1 | |
| _ami_image_id=$(aws ec2 describe-images --output text \ | |
| --filters "Name=name,Values=${AMI_NAME}" \ | |
| --query 'Images[*].[ImageId]') | |
| if [ -z "$_ami_image_id" ]; then | |
| exit 1 | |
| fi | |
| echo "ami_image_id=${_ami_image_id}" >>"${GITHUB_OUTPUT}" | |
| for subnet_id in ${AWS_SUBNET_IDS}; do | |
| _instance_id=$(aws ec2 run-instances --image-id "${_ami_image_id}" --count 1 \ | |
| --instance-type '${{ matrix.ec2_instance_type }}' \ | |
| --tag-specifications \ | |
| "ResourceType=instance,Tags=[{Key=Name,Value=test-${AMI_NAME}}]" \ | |
| "ResourceType=volume,Tags=[{Key=Name,Value=test-${AMI_NAME}}]" \ | |
| --subnet-id "${subnet_id}" \ | |
| --security-group-ids "${AWS_SECURITY_GROUP_ID}" \ | |
| --key-name "${AWS_EC2_KEY_NAME}" \ | |
| --metadata-options "HttpEndpoint=enabled,HttpTokens=${AWS_EC2_METADATA_V2}" \ | |
| --user-data "file://${CONFIG_JSON}" | jq -r '.Instances[0].InstanceId' || true) | |
| [ -n "$_instance_id" ] && break | |
| done | |
| if [ -z "${_instance_id}" ]; then | |
| exit 1 | |
| fi | |
| echo "instance_id=${_instance_id}" >>"${GITHUB_OUTPUT}" | |
| aws ec2 wait instance-running --instance-ids "${_instance_id}" | |
| aws ec2 wait instance-status-ok --instance-ids "${_instance_id}" | |
| ssh-add -l | |
| # FIXME: switch to ssh-uuid | |
| until echo 'balena ps -q -f name=balena_supervisor | xargs balena inspect \ | |
| | jq -r ".[] | select(.State.Health.Status!=null).Name + \":\" + .State.Health.Status"; exit' \ | |
| | balena device ssh "${UUID}" | grep -q ":healthy"; do | |
| echo "::info::Waiting for balena-supervisor..." | |
| sleep $(( (RANDOM % 30) + 30 ))s | |
| done | |
| - name: Make AMI public | |
| if: steps.test.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| env: | |
| AMI_ARCHITECTURE: ${{ steps.ami.outputs.arch }} | |
| AMI_IMAGE_ID: ${{ steps.test.outputs.ami_image_id }} | |
| AWS_DEFAULT_REGION: ${{ env.AWS_DEFAULT_REGION }} | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-quotas.html | |
| AWS_AMI_PUBLIC_QUOTA: 2 | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 0 | |
| _ami_public_images_count=$(aws ec2 describe-images \ | |
| --owners "self" \ | |
| --filters "Name=name,Values=${AMI_NAME%%-*}-*" \ | |
| "Name=architecture,Values=${AMI_ARCHITECTURE}" \ | |
| "Name=is-public,Values=true" | jq '.Images | length') | |
| if [ "$_ami_public_images_count" -ge "$AWS_AMI_PUBLIC_QUOTA" ]; then | |
| _ami_oldest_image_id=$(aws ec2 describe-images \ | |
| --owners "self" \ | |
| --filters "Name=name,Values=${AMI_NAME%%-*}-*" \ | |
| "Name=architecture,Values=${AMI_ARCHITECTURE}" \ | |
| "Name=is-public,Values=true" \ | |
| --query 'sort_by(Images, &CreationDate)[0].ImageId') | |
| if [ -n "$_ami_oldest_image_id" ]; then | |
| if [ "$(aws ec2 describe-images --image-ids "${_ami_oldest_image_id}" \ | |
| | jq -r '.Images[].Public')" = "true" ]; then | |
| if aws ec2 modify-image-attribute \ | |
| --image-id "${_ami_oldest_image_id}" \ | |
| --launch-permission '{"Remove":[{"Group":"all"}]}'; then | |
| if ! [ "$(aws ec2 describe-images --image-ids "${_ami_oldest_image_id}" \ | |
| | jq -r '.Images[].Public')" = "false" ]; then | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| fi | |
| fi | |
| _ami_snapshot_id=$(aws ec2 describe-images \ | |
| --region="${AWS_DEFAULT_REGION}" \ | |
| --image-ids "${AMI_IMAGE_ID}" | jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId') | |
| if [ -n "$_ami_snapshot_id" ]; then | |
| if aws ec2 modify-snapshot-attribute --operation-type add \ | |
| --region "${AWS_DEFAULT_REGION}" \ | |
| --snapshot-id "${_ami_snapshot_id}" \ | |
| --attribute createVolumePermission \ | |
| --group-names all; then | |
| if ! [ "$(aws ec2 describe-snapshot-attribute \ | |
| --region "${AWS_DEFAULT_REGION}" \ | |
| --snapshot-id "${_ami_snapshot_id}" \ | |
| --attribute createVolumePermission | jq -r '.CreateVolumePermissions[].Group')" == "all" ]; then | |
| exit 1 | |
| fi | |
| fi | |
| else | |
| exit 1 | |
| fi | |
| if aws ec2 modify-image-attribute --image-id "${AMI_IMAGE_ID}" --launch-permission "Add=[{Group=all}]"; then | |
| if ! [ "$(aws ec2 describe-images --image-ids "${AMI_IMAGE_ID}" | jq -r '.Images[].Public')" = "true" ]; then | |
| exit 1 | |
| fi | |
| fi | |
| - name: Set image deprecation | |
| if: steps.test.outcome == 'success' && steps.check.outputs.skip != 'true' | |
| env: | |
| AMI_IMAGE_ID: ${{ steps.test.outputs.ami_image_id }} | |
| PERIOD: "now +1 year" | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 0 | |
| deprecate_at=$(date +%Y-%m-%dT%H:%M:%SZ -d "${PERIOD}") | |
| aws ec2 enable-image-deprecation \ | |
| --image-id "${AMI_IMAGE_ID}" \ | |
| --deprecate-at "${deprecate_at}" | |
| - name: Clean up EoL images | |
| continue-on-error: true | |
| if: always() | |
| env: | |
| AMI_NAME: ${{ steps.ami.outputs.name }} | |
| # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-quotas.html | |
| PERIOD: "1 years ago" | |
| run: | | |
| [ -z "$AMI_NAME" ] && exit 0 | |
| _date=$(date +%Y-%m-%d -d "${PERIOD}") | |
| image_ids=$(aws ec2 describe-images --output text \ | |
| --filters "Name=name,Values=${AMI_NAME%%-*}-*" \ | |
| --owners "self" \ | |
| --query 'Images[?CreationDate<`'"${_date}"'`].[ImageId]') | |
| for image_id in ${image_ids}; do | |
| _snapshots="$(aws ec2 describe-images --output text\ | |
| --image-ids "${image_id}" \ | |
| --query 'Images[*].BlockDeviceMappings[*].Ebs.SnapshotId')" | |
| if aws ec2 deregister-image --image-id "${image_id}"; then | |
| if [ -n "$_snapshots" ]; then | |
| for snapshot in ${_snapshots}; do | |
| if ! aws ec2 delete-snapshot --snapshot-id "${snapshot}"; then | |
| echo "::warning::Could not remove snapshot ${snapshot}" | |
| fi | |
| done | |
| fi | |
| else | |
| echo "::warning::Could not de-register image ${image_id}" | |
| fi | |
| done | |
| - name: Clean up image on failure | |
| continue-on-error: true | |
| if: failure() || cancelled() | |
| env: | |
| AMI_IMAGE_ID: ${{ steps.image.outputs.id }} | |
| run: | | |
| if [ -n "$AMI_IMAGE_ID" ]; then | |
| aws ec2 deregister-image --image-id "${AMI_IMAGE_ID}" | |
| fi | |
| - name: Clean up snapshot on failure | |
| continue-on-error: true | |
| if: failure() || cancelled() | |
| env: | |
| AMI_SNAPSHOT_ID: ${{ steps.snapshot.outputs.id }} | |
| run: | | |
| if [ -n "$AMI_SNAPSHOT_ID" ]; then | |
| aws ec2 delete-snapshot --snapshot-id "${AMI_SNAPSHOT_ID}" | |
| fi | |
| - name: Terminate AWS/EC2 test instance | |
| continue-on-error: true | |
| if: always() | |
| env: | |
| EC2_INSTANCE_ID: ${{ steps.test.outputs.instance_id }} | |
| run: | | |
| [[ -z "$EC2_INSTANCE_ID" ]] && exit 0 | |
| aws ec2 terminate-instances --instance-ids "${EC2_INSTANCE_ID}" | |
| - name: Delete temporary image from AWS/S3 | |
| continue-on-error: true | |
| if: always() | |
| env: | |
| S3_URL: ${{ steps.s3.outputs.url }} | |
| run: | | |
| [[ -z "$S3_URL" ]] && exit 0 | |
| aws s3 rm "${S3_URL}" | |
| - name: remove AWS/EC2 key-pair | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| AWS_EC2_KEY_NAME: ${{ steps.generate-key-pair.outputs.key_name }} | |
| run: | | |
| [ -z "$AWS_EC2_KEY_NAME" ] && exit 0 | |
| aws ec2 delete-key-pair --key-name "${AWS_EC2_KEY_NAME}" | |
| - name: Clean up test fleets | |
| continue-on-error: true | |
| if: always() | |
| env: | |
| FLEETS: '${{ steps.preload-fleet.outputs.name }} ${{ steps.test-fleet.outputs.name }}' | |
| run: | | |
| for fleet in ${FLEETS}; do | |
| _key_id=$(balena ssh-key list | grep "${fleet#*/}" | awk '{print $1}' || echo) | |
| if [ -n "$_key_id" ]; then | |
| balena ssh-key rm "${_key_id}" --yes | |
| fi | |
| balena fleet rm "${fleet}" --yes | |
| done | |
| - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ github.event.pull_request.head.sha }} |