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

rewrite nixpkgs's create-amis script using Terraform #254

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/create-amis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Create NixOS AMIs

on:
workflow_dispatch:
workflow_call:
inputs:
release:
required: true
type: string
architecture:
required: true
type: string
default: x86_64
build_nr:
required: true
type: number
description: hydra.nixos.org image build number

env:
AWS_REGION: "eu-west-1"

jobs:
create-nixos-amis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v3
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::080433136561:role/nixos-image-creator
role-session-name: gha-terraform-deploy-amis
aws-region: ${{ env.AWS_REGION }}
- working-directory: amis
id: get-build
run: |
echo "build_json=$(curl -H 'Content-type: application/json' \
https://hydra.nixos.org/build/${{inputs.build_nr}})" > "$GITHUB_OUTPUT"
- working-directory: amis
id: get-store-path
run: |
echo '${{steps.get-build.outputs.build_json}}' \
| nix develop --command jq '.buildoutputs|.out|"store_path=\(.path)"' -r > "$GITHUB_OUTPUT"
- run: nix-store -r ${{steps.get-store-path.outputs.store_path}}
- run: nix develop --command jq -f regions.jq regions.json > copy.tf.json
working-directory: amis
- run: |
nix develop --command terraform init
working-directory: amis
- run: |
nix develop --command \
terraform workspace select -or-create \
${{inputs.release}}.${{inputs.architecture}}.${{inputs.build_nr}}
working-directory: amis
- run: |
nix develop --command \
terraform apply -auto-approve \
-var image_store_path=${{steps.get-store-path.outputs.store_path}}
working-directory: amis
22 changes: 22 additions & 0 deletions .github/workflows/release-amis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Push current NixOS release AMIs

on:
workflow_dispatch:

permissions:
id-token: write
contents: read

jobs:
x86_64-amis:
uses: ./.github/workflows/create-amis.yml
with:
build_nr: 222822268
architecture: x86_64
release: "23.05"
aarch64-amis:
uses: ./.github/workflows/create-amis.yml
with:
build_nr: 222822272
architecture: aarch64
release: "23.05"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@

# Terraform
.terraform*
*tfstate
*tfstate.backup
*.aarch64.*.tfvars
*.x86_64.*.tfvars
.current-workspace
copy.tf.json
25 changes: 25 additions & 0 deletions amis/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.PHONY: plan apply

arch=x86_64
version=23.05
build-nr=

tfvarsFile=$(version).$(arch).current.tfvars

$(tfvarsFile) .current-workspace:
./pull-latest $(version) $(arch) $(build-nr)

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)

destroy: copy.tf.json $(tfvarsFile)
@terraform workspace select $(shell cat .current-workspace)
@terraform destroy -var-file=$(tfvarsFile)
60 changes: 60 additions & 0 deletions amis/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions amis/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
inputs = {
nixpkgs.url = "nixpkgs/master";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = flakes @ { self, nixpkgs, flake-utils }:

flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells = {
default = with pkgs;
mkShell {
buildInputs = [
jq
(terraform.withPlugins (p: with p; [
aws
]))
];
};
};
});
}
12 changes: 12 additions & 0 deletions amis/generate-nixpkgs-update
Original file line number Diff line number Diff line change
@@ -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
108 changes: 108 additions & 0 deletions amis/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
}
16 changes: 16 additions & 0 deletions amis/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
locals {
image_tags = {
Name = local.image_name
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
}
}
31 changes: 31 additions & 0 deletions amis/pull-latest
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#! /usr/bin/env bash
#

hydraJob="nixos.amazonImage"
baseUrl="https://hydra.nixos.org/job/nixos"

version="$1"
arch="$2"
buildNr="$3"

if [[ -z "$buildNr" ]]
then
buildUrl="${baseUrl}/release-${version}-small/${hydraJob}.${arch}-linux/latest"
else
buildUrl="${baseUrl}/build/${buildNr}"
fi


build=$(curl -sL -H 'Content-type: application/json' "$buildUrl")

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
Loading