Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud images (testing, documentation, releases) #3102

Open
Tracked by #2129
mudler opened this issue Jan 7, 2025 · 3 comments
Open
Tracked by #2129

Cloud images (testing, documentation, releases) #3102

mudler opened this issue Jan 7, 2025 · 3 comments
Assignees

Comments

@mudler
Copy link
Member

mudler commented Jan 7, 2025

  • Publish images to market place of relevant cloud
  • Create videos of how using images
  • Create videos of how to build cloud images from Kairos releases and container images
@jimmykarily jimmykarily moved this to In Progress 🏃 in 🧙Issue tracking board Jan 13, 2025
@jimmykarily jimmykarily moved this from In Progress 🏃 to Todo 🖊 in 🧙Issue tracking board Jan 13, 2025
@jimmykarily
Copy link
Contributor

jimmykarily commented Jan 13, 2025

Let's start with AWS first. No need to do them all in one go. We should be able to spin up an EC2 instance of Kairos with our custom cloud config as datasource.

@jimmykarily jimmykarily moved this from Todo 🖊 to In Progress 🏃 in 🧙Issue tracking board Jan 14, 2025
@jimmykarily jimmykarily self-assigned this Jan 14, 2025
@jimmykarily
Copy link
Contributor

I'm preparing a script that automated the uploading of raw images to aws. It's affected by this bug: aws/aws-cli#9047 (comment)

The problem is that there is no way to check if a snapshot for the same s3 file has already been uploaded in order to skip it (trying to make the script idempotent). Also adding tags to the snapshot doesn't seem to work (to be used for looking it up).

I'll figure something else out.

@jimmykarily
Copy link
Contributor

Here is my current version of that script:

#!/bin/bash

# Given a raw image created with Auroraboot, this script will upload it to the speficied AWS account as a public AMI.
# Docs:
# https://docs.aws.amazon.com/vm-import/latest/userguide/required-permissions.html
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/creating-an-ami-ebs.html#creating-launching-ami-from-snapshot
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-ami-boot-mode.html
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/launch-instance-boot-mode.html

set -e

checkArguments() {
  if [ $# -lt 1 ]; then
    echo "Error: You need to specify the cloud image to upload."
    echo "Usage: $0 <cloud-image>"
    exit 1
  fi

  local file="$1"

  if [ ! -f "$file" ]; then
    echo "Error: File '$file' does not exist."
    exit 1
  fi

  if ! file "$file" | grep -q 'DOS/MBR boot sector'; then
    echo "Error: File '$file' is not a raw image."
    exit 1
  fi
}

checkEnvVars() {
  if [ -z "$AWS_PROFILE" ] || [ -z "$AWS_REGION" ] || [ -z "$AWS_S3_BUCKET" ]; then
    echo "Error: AWS_PROFILE, AWS_REGION and AWS_S3_BUCKET environment variables must be set."
    exit 1
  fi
}

AWS() {
  aws --profile $AWS_PROFILE --region $AWS_REGION $@
}

# https://docs.aws.amazon.com/vm-import/latest/userguide/required-permissions.html#vmimport-role
ensureVmImportRole() {
  (AWS iam list-roles | jq -r '.Roles[] | select(.RoleName | contains("vmimport")) | .RoleName' | grep -q "vmimport" && echo "vmimport role found. All good.") || {
    echo "Creating vmimport role"

    export AWS_PAGER="" # Avoid being dropped to a pager
    AWS iam create-role --role-name vmimport --assume-role-policy-document file://<(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "vmie.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "vmimport"
        }
      }
    }
  ]
}
EOF
)

  #  AWS iam attach-role-policy --role-name vmimport --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM

    AWS iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://<(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:GetBucketAcl",
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::$AWS_S3_BUCKET",
        "arn:aws:s3:::$AWS_S3_BUCKET/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CancelConversionTask",
        "ec2:CancelExportTask",
        "ec2:CreateImage",
        "ec2:CreateInstanceExportTask",
        "ec2:CreateTags",
        "ec2:ExportImage",
        "ec2:ImportInstance",
        "ec2:ImportVolume",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:TerminateInstances",
        "ec2:ImportImage",
        "ec2:ImportSnapshot",
        "ec2:CancelImportTask",
        "ec2:ModifySnapshotAttribute",
        "ec2:CopySnapshot",
        "ec2:RegisterImage",
        "ec2:Describe*"
      ],
      "Resource": "*"
    }
  ]
}
EOF
)

    sleep 10 # Wait for the policy and permissions to be effective. This is not ideal but I couldn't find any better way.
  }
}

uploadImageToS3() {
  local file="$1"
  local s3Path="s3://$AWS_S3_BUCKET/$file"

  if AWS s3 ls "$AWS_S3_BUCKET/$file" > /dev/null 2>&1; then
    echo "File '$file' already exists in S3 bucket '$AWS_S3_BUCKET'."
  else
    echo "File '$file' does not exist in S3 bucket '$AWS_S3_BUCKET'. Uploading now."
    AWS s3 cp $1 s3://$AWS_S3_BUCKET/$1
  fi
}

waitForSnapshotCompletion() {
  local snapshotID="$1"
  local status=""

  while true; do
    status=$(AWS ec2 describe-import-snapshot-tasks --import-task-ids "$snapshotID" --query 'ImportSnapshotTasks[0].SnapshotTaskDetail.Status' --output text)

    if [ "$status" == "completed" ]; then
      echo "Snapshot import completed."
      break
    elif [ "$status" == "deleted" ] || [ "$status" == "cancelling" ] || [ "$status" == "cancelled" ]; then
      echo "Snapshot import failed with status: $status"
      exit 1
    else
      echo "Waiting for snapshot import to complete. Current status: $status" >&2
      sleep 30
    fi
  done
}

importAsSnapshot() {
  local file="$1"
  local snapshotID

  snapshotID=$(AWS ec2 describe-snapshots --filters "Name=tag:SourceFile,Values=$file" --query "Snapshots[0].SnapshotId" --output text)
  if [ "$snapshotID" != "None" ]; then
    return $snapshotID
  fi

  snapshotID=$(AWS ec2 import-snapshot --description "$file" \
    --tag-specifications "ResourceType=snapshot,Tags=[{Key=source,Value=$file}]" \
    --disk-container file://<(cat <<EOF
{
  "Description": "$file",
  "Format": "RAW",
  "UserBucket": {
    "S3Bucket": "$AWS_S3_BUCKET",
    "S3Key": "$file"
  }
}
EOF
  ) --query 'ImportTaskId' --output text)
  if [ $? -ne 0 ]; then
    echo "Failed to import snapshot" >&2
    return 1
  fi

  echo "Snapshot import task started with ID: $snapshotID" >&2

  waitForSnapshotCompletion "$snapshotID"

  echo "Adding tag to the snapshot"
  AWS ec2 create-tags --resources "$snapshotID" --tags Key=SourceFile,Value="$file"

  echo "$snapshotID" # Return the snapshot ID so that we can grab it with `tail -1`
}

# aws --profile kairos --region eu-central-1 ec2 register-image     --name "Kairos ubuntu 24.04 - test"     --description "AMI created from snapshot"     --architecture x86_64     --root-device-name /dev/xvda     --block-device-mappings "[{\"DeviceName\":\"/dev/xvda\",\"Ebs\":{\"SnapshotId\":\"snap-0bd3002385dcc6e5a\"}}]"     --virtualization-type hvm --boot-mode uefi --ena-support


checkImageExistsOrCreate() {
  local imageName="$1"
  local snapshotID="$2"
  local imageID

  # Check if the image already exists
  imageID=$(AWS ec2 describe-images --filters "Name=name,Values=$imageName" --query 'Images[0].ImageId' --output text)

  if [ "$imageID" != "None" ]; then
    echo "Image '$imageName' already exists with Image ID: $imageID"
  else
    echo "Image '$imageName' does not exist. Creating from snapshot..."

    imageID=$(AWS ec2 register-image \
      --name "$imageName" \
      --description "AMI created from snapshot" \
      --architecture x86_64 \
      --root-device-name /dev/xvda \
      --block-device-mappings "[{\"DeviceName\":\"/dev/xvda\",\"Ebs\":{\"SnapshotId\":\"$snapshotID\"}}]" \
      --virtualization-type hvm \
      --boot-mode uefi \
      --ena-support \
      --query 'ImageId' \
      --output text)

    echo "Image '$imageName' created with Image ID: $imageID"
  fi
}

# ----- Main script -----
checkEnvVars
checkArguments "$@"
ensureVmImportRole
uploadImageToS3 $1
output=$(importAsSnapshot $1)
if [ $? -ne 0 ]; then
  echo "Failed to import snapshot"
  exit 1
fi
snapshotID=$(echo "$output" | tail -1)
checkImageExistsOrCreate $1 $snapshotID

# TODO
# - make AMI public
# - tag the snapshot to be able to match it with the AMI?

Currently it takes care of setting up the vmimport role as per the aws docs. This is only required once but I put it here for reference (also adds a bit more permissions that those in the docs, might need cleanup).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress 🏃
Development

No branches or pull requests

2 participants