From 6c8ebb8266110d4c7460d97a7f074605b249820d Mon Sep 17 00:00:00 2001 From: Amine Chikhaoui Date: Fri, 2 Jun 2023 00:21:37 -0400 Subject: [PATCH] rewrite nixpkgs's create-amis script using Terraform --- .gitignore | 7 +++ amis/.terraform.lock.hcl | 24 ++++++++ amis/Makefile | 20 +++++++ amis/generate-nixpkgs-update | 12 ++++ amis/main.tf | 108 +++++++++++++++++++++++++++++++++++ amis/providers.tf | 17 ++++++ amis/pull-latest | 23 ++++++++ amis/regions.jq | 38 ++++++++++++ amis/regions.json | 23 ++++++++ amis/state.tf | 15 +++++ amis/variables.tf | 18 ++++++ 11 files changed, 305 insertions(+) create mode 100644 amis/.terraform.lock.hcl create mode 100644 amis/Makefile create mode 100755 amis/generate-nixpkgs-update create mode 100644 amis/main.tf create mode 100644 amis/providers.tf create mode 100755 amis/pull-latest create mode 100644 amis/regions.jq create mode 100644 amis/regions.json create mode 100644 amis/state.tf create mode 100644 amis/variables.tf diff --git a/.gitignore b/.gitignore index 76bf9349..44ba2f80 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,10 @@ # Terraform .terraform* +!.terraform.lock.hcl +*tfstate +*tfstate.backup +*.aarch64.*.tfvars +*.x86_64.*.tfvars +.current-workspace +copy.tf.json diff --git a/amis/.terraform.lock.hcl b/amis/.terraform.lock.hcl new file mode 100644 index 00000000..40ae7df3 --- /dev/null +++ b/amis/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.0.1" + hashes = [ + "h1:Jz41xV1uwYaT5TQGW05seb2vHrwMtRJh19K7MArqoYc=", + "zh:006daf4060087b5f0c13562beed33f524a6f9e04ebd72a782bfe60502076368f", + "zh:0f49636550aadd373c7e5c710600901c2f153ddd71b6c50482e1afdbb3f8d95d", + "zh:1999d2fad0a7a884aab0d191507cf895df0ea7201369a2ef37529f4253ce1065", + "zh:1b51774866cddca5a2da5a09a316e9ca078fc821f47611a184245ca892e9335d", + "zh:2875579acceba1403563c4281c76a3a9b53b970ed6494e5370e27efb6430bb50", + "zh:349eb9ab7c026b72154ce55c7bf9a69ebb3c3a4745ecfdb0c593400762ed1b0c", + "zh:38f96c14db5b3beb80748010c0a97dd097a303b24c8478a1286ce1f48a1a0375", + "zh:3d212e6e4fc54584e47faeccf501e5a68266c7fe9e36d89ad787c2e1f0e86197", + "zh:3ea61ab960ef34ff66457319b9083c8645a9f801f7b5578e7e3f616e26945f90", + "zh:584db6d88a07cac639f746104ccd5ed5c517ed99f892a143dad3bb64023098fc", + "zh:653def88ffa17b628459f942e743d30ab9fc2194af464d88258a784d9282f9f9", + "zh:9737008fea7ffbf5782fceb0108a283e91992c47bfcb93ec55ef43deaa7e509d", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ce3ba0cabc1704c584cc46bf1432b14ba1d34b1a30e03a5694b5940cf1673ab8", + "zh:de3e6d4e1defc6032359fc229000a1458d777adb07974293f194dde069adcc04", + ] +} diff --git a/amis/Makefile b/amis/Makefile new file mode 100644 index 00000000..9dc53c84 --- /dev/null +++ b/amis/Makefile @@ -0,0 +1,20 @@ +.PHONY: plan apply + +arch=x86_64 +version=23.05 + +tfvarsFile=$(version).$(arch).current.tfvars + +$(tfvarsFile) .current-workspace: + ./pull-latest $(version) $(arch) + +copy.tf.json: regions.jq regions.json + @jq -f regions.jq regions.json > copy.tf.json + +plan: copy.tf.json $(tfvarsFile) .current-workspace + @terraform workspace select -or-create $(shell cat .current-workspace) + @terraform plan -var-file=$(tfvarsFile) + +apply: copy.tf.json $(tfvarsFile) + @terraform workspace select -or-create $(shell cat .current-workspace) + @terraform apply -var-file=$(tfvarsFile) diff --git a/amis/generate-nixpkgs-update b/amis/generate-nixpkgs-update new file mode 100755 index 00000000..59614f16 --- /dev/null +++ b/amis/generate-nixpkgs-update @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +version="$1" + +echo "append the following to nixpkgs: nixos/modules/virtualisation/amazon-ec2-amis.nix" +echo "make sure to update latest attribute to point to $version !" +echo + +terraform output -json \ + | jq \ + --arg version "$version" \ + '.[]|.value|"\"\($version)\".\(.region).\(.arch).hvm-ebs = \(.id);"' -r diff --git a/amis/main.tf b/amis/main.tf new file mode 100644 index 00000000..f50b9136 --- /dev/null +++ b/amis/main.tf @@ -0,0 +1,108 @@ +locals { + image_info_file = file("${var.image_store_path}/nix-support/image-info.json") + + image_info = jsondecode(local.image_info_file) + image_system = local.image_info.system + root_disk = local.image_info.disks.root + + is_zfs = lookup(local.image_info.disks, "boot", null) != null ? true : false + zfs_boot = local.is_zfs ? local.image_info.disks.boot : null + + label_suffix = local.is_zfs ? "-ZFS" : "" + image_label = "${local.image_info.label}${local.label_suffix}" + image_name = "NixOS-${local.image_label}-${local.image_system}" + image_description = "NixOS ${local.image_label} ${local.image_system}" + + arch_mapping = { + "aarch64-linux" = "arm64" + "x86_64-linux" = "x86_64" + } + + image_logical_bytes = ( + local.is_zfs ? local.zfs_boot.logical_bytes : local.root_disk.logical_bytes + ) + image_logical_gigabytes = floor( + (local.image_logical_bytes - 1) / 1024 / 1024 / 1024 + 1 + ) + zfs_boot_file = local.is_zfs ? { boot = local.zfs_boot.file } : {} + image_files = merge( + { root = local.root_disk.file } + , local.zfs_boot_file + ) +} + +resource "aws_s3_object" "image_file" { + for_each = local.image_files + bucket = var.bucket + key = trimprefix(each.value, "/") + source = each.value +} + +resource "aws_ebs_snapshot_import" "image_import" { + for_each = aws_s3_object.image_file + disk_container { + description = "nixos-image-${local.image_label}-${local.image_system}" + format = "VHD" + user_bucket { + s3_bucket = each.value.bucket + s3_key = each.value.key + } + } + + role_name = var.service_role_name +} + +locals { + # When ZFS is used the boot device is "boot" + boot_snapshot = (local.is_zfs ? + aws_ebs_snapshot_import.image_import["boot"] : + aws_ebs_snapshot_import.image_import["root"] + ) +} + +resource "aws_ami" "nixos_ami" { + name = local.image_name + virtualization_type = "hvm" + root_device_name = "/dev/xvda" + architecture = local.arch_mapping[local.image_system] + boot_mode = local.image_info.boot_mode + ena_support = true + sriov_net_support = "simple" + + ebs_block_device { + device_name = "/dev/xvda" + snapshot_id = local.boot_snapshot.id + volume_size = local.boot_snapshot.volume_size + delete_on_termination = true + volume_type = "gp3" + } + + dynamic "ebs_block_device" { + for_each = local.is_zfs ? { zfs = true } : {} + + content { + device_name = "/dev/xvdb" + snapshot_id = aws_ebs_snapshot_import.image_import["root"].id + volume_size = aws_ebs_snapshot_import.image_import["root"].id + delete_on_termination = true + volume_type = "gp3" + } + } + + lifecycle { + ignore_changes = [deprecation_time] + } +} + +resource "aws_ami_launch_permission" "public_access" { + image_id = aws_ami.nixos_ami.id + group = "all" +} + +output "ami" { + value = { + region = var.aws_region + arch = local.image_system + id = aws_ami.nixos_ami.id + } +} diff --git a/amis/providers.tf b/amis/providers.tf new file mode 100644 index 00000000..94ccc723 --- /dev/null +++ b/amis/providers.tf @@ -0,0 +1,17 @@ +locals { + image_tags = { + # TODO remove the testing prefix + Name = "${local.image_name} - terraform testing" + Description = local.image_description + System = local.image_system + Release = terraform.workspace + Official = false + } +} + +provider "aws" { + region = "eu-west-1" + default_tags { + tags = local.image_tags + } +} diff --git a/amis/pull-latest b/amis/pull-latest new file mode 100755 index 00000000..350778a1 --- /dev/null +++ b/amis/pull-latest @@ -0,0 +1,23 @@ +#! /usr/bin/env bash +# + +version="$1" +arch="$2" +hydraJob="nixos.amazonImage" +baseUrl="https://hydra.nixos.org/job/nixos" + +build=$(curl -sL \ + -H 'Content-type: application/json' \ + "${baseUrl}/release-${version}-small/${hydraJob}.${arch}-linux/latest") + +storePath=$(echo "$build" | jq '.buildoutputs|.out|.path' -r) +buildId=$(echo "$build" | jq .id -r) + +nix-store -r "${storePath}" + +tfvarsFile="${version}.${arch}.${buildId}.tfvars" + +echo "image_store_path = \"${storePath}\"" > "$tfvarsFile" +ln -s "$tfvarsFile" "${version}.${arch}.current.tfvars" + +echo "${version}.${arch}.${buildId}" > .current-workspace diff --git a/amis/regions.jq b/amis/regions.jq new file mode 100644 index 00000000..a06bc3dc --- /dev/null +++ b/amis/regions.jq @@ -0,0 +1,38 @@ +[ + .[]| + { "provider": { + "aws": { + "region": . , + "alias": . + } + }, + "resource": { + "aws_ami_copy": { + "copy_\(.)": { + "name": "${local.image_name}", + "description": "${local.image_description}", + "source_ami_id": "${aws_ami.nixos_ami.id}", + "source_ami_region": "${var.aws_region}", + "provider": "aws.\(.)", + "lifecycle": { "ignore_changes": [ "deprecation_time" ] } + } + }, + "aws_ami_launch_permission": { + "public_access_\(.)": { + "image_id": "${aws_ami_copy.copy_\(.).id}", + "group": "all", + "provider": "aws.\(.)" + } + } + }, + "output": { + "ami_\(.)": { + "value": { + "id": "${aws_ami_copy.copy_\(.).id}", + "arch": "${local.image_system}", + "region": . + } + } + } + } +] diff --git a/amis/regions.json b/amis/regions.json new file mode 100644 index 00000000..aeee99b5 --- /dev/null +++ b/amis/regions.json @@ -0,0 +1,23 @@ +[ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-2", + "eu-west-3", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" +] diff --git a/amis/state.tf b/amis/state.tf new file mode 100644 index 00000000..30530260 --- /dev/null +++ b/amis/state.tf @@ -0,0 +1,15 @@ +terraform { + backend "s3" { + bucket = "nixos-terraform-state" + encrypt = true + key = "nixos-amis.tfstate" + workspace_key_prefix = "targets/amis/releases" + region = "eu-west-1" + } + + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} diff --git a/amis/variables.tf b/amis/variables.tf new file mode 100644 index 00000000..a1383250 --- /dev/null +++ b/amis/variables.tf @@ -0,0 +1,18 @@ +variable "aws_region" { + type = string + default = "eu-west-1" +} + +variable "bucket" { + type = string + default = "nixos-amis" +} + +variable "service_role_name" { + type = string + default = "vmimport" +} + +variable "image_store_path" { + type = string +}