From 9a61b7cbc3bb306a086a35f558e12a0dc6fbc84b Mon Sep 17 00:00:00 2001 From: Phorcys <57866459+phorcys420@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:59:07 +0000 Subject: [PATCH 1/4] feat(aws-devcontainer): use hashicorp/cloud-init provider --- .../cloud-init/cloud-config.yaml.tftpl | 15 ++ .../cloud-init/userdata.sh.tftpl | 38 +++++ examples/templates/aws-devcontainer/main.tf | 148 +++++++----------- 3 files changed, 113 insertions(+), 88 deletions(-) create mode 100644 examples/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl create mode 100644 examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl diff --git a/examples/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl b/examples/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl new file mode 100644 index 0000000000000..af6b35171ca30 --- /dev/null +++ b/examples/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl @@ -0,0 +1,15 @@ +#cloud-config +cloud_final_modules: + - [scripts-user, always] +hostname: ${hostname} +users: + - name: ${linux_user} + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + ssh_authorized_keys: + - "${ssh_pubkey}" +# Automatically grow the partition +growpart: + mode: auto + devices: ['/'] + ignore_growroot_disabled: false diff --git a/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl b/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl new file mode 100644 index 0000000000000..ddadc2b62ad6c --- /dev/null +++ b/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl @@ -0,0 +1,38 @@ +#!/bin/bash +# Install Docker +if ! command -v docker &> /dev/null +then + echo "Docker not found, installing..." + curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh 2>&1 >/dev/null + usermod -aG docker ${linux_user} + newgrp docker +else + echo "Docker is already installed." +fi + +# Set up Docker credentials +mkdir -p "/home/${linux_user}/.docker" + +# NOTE: do we want to use the templating engine's ifs instead? +if [ -n "${docker_config_json_base64}" ]; then + # Write the Docker config JSON to disk if it is provided. + printf "%s" "${docker_config_json_base64}" | base64 -d | tee "/home/${linux_user}/.docker/config.json" +else + # Assume that we're going to use the instance IAM role to pull from the cache repo if we need to. + # Set up the ecr credential helper. + apt-get update -y && apt-get install -y amazon-ecr-credential-helper + mkdir -p .docker + printf '{"credsStore": "ecr-login"}' | tee "/home/${linux_user}/.docker/config.json" +fi +chown -R ${linux_user}:${linux_user} "/home/${linux_user}/.docker" + +# Start envbuilder +sudo -u coder docker run \ + --rm \ + --net=host \ + -h ${hostname} \ + -v /home/${linux_user}/envbuilder:/workspaces \ + %{ for key, value in environment ~} + -e ${key}="${value}" \ + %{ endfor ~} + ${builder_image} diff --git a/examples/templates/aws-devcontainer/main.tf b/examples/templates/aws-devcontainer/main.tf index 27434385c647b..7d0decfef6a61 100644 --- a/examples/templates/aws-devcontainer/main.tf +++ b/examples/templates/aws-devcontainer/main.tf @@ -6,6 +6,9 @@ terraform { aws = { source = "hashicorp/aws" } + cloudinit = { + source = "hashicorp/cloudinit" + } envbuilder = { source = "coder/envbuilder" } @@ -153,13 +156,16 @@ data "aws_iam_instance_profile" "vm_instance_profile" { locals { # TODO: provide a way to pick the availability zone. aws_availability_zone = "${module.aws_region.value}a" - linux_user = "coder" - # Name the container after the workspace and owner. - container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + + hostname = lower(data.coder_workspace.me.name) + linux_user = "coder" + # The devcontainer builder image is the image that will build the devcontainer. devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value + # We may need to authenticate with a registry. If so, the user will provide a path to a docker config.json. docker_config_json_base64 = try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, "") + # The envbuilder provider requires a key-value map of environment variables. Build this here. envbuilder_env = { # ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider @@ -172,7 +178,7 @@ locals { # The agent init script is required for the agent to start up. We base64 encode it here # to avoid quoting issues. "ENVBUILDER_INIT_SCRIPT" : "echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh", - "ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""), + "ENVBUILDER_DOCKER_CONFIG_BASE64" : local.docker_config_json_base64, # The fallback image is the image that will run if the devcontainer fails to build. "ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value, # The following are used to push the image to the cache repo, if defined. @@ -181,87 +187,6 @@ locals { # You can add other required environment variables here. # See: https://github.com/coder/envbuilder/?tab=readme-ov-file#environment-variables } - # If we have a cached image, use the cached image's environment variables. Otherwise, just use - # the environment variables we've defined above. - docker_env_input = try(envbuilder_cached_image.cached.0.env_map, local.envbuilder_env) - # Convert the above to the list of arguments for the Docker run command. - # The startup script will write this to a file, which the Docker run command will reference. - docker_env_list_base64 = base64encode(join("\n", [for k, v in local.docker_env_input : "${k}=${v}"])) - # Builder image will either be the builder image parameter, or the cached image, if cache is provided. - builder_image = try(envbuilder_cached_image.cached[0].image, data.coder_parameter.devcontainer_builder.value) - # User data to start the workspace. - user_data = <<-EOT - Content-Type: multipart/mixed; boundary="//" - MIME-Version: 1.0 - - --// - Content-Type: text/cloud-config; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: attachment; filename="cloud-config.txt" - - #cloud-config - cloud_final_modules: - - [scripts-user, always] - hostname: ${lower(data.coder_workspace.me.name)} - users: - - name: ${local.linux_user} - sudo: ALL=(ALL) NOPASSWD:ALL - shell: /bin/bash - ssh_authorized_keys: - - "${data.coder_parameter.ssh_pubkey.value}" - # Automatically grow the partition - growpart: - mode: auto - devices: ['/'] - ignore_growroot_disabled: false - - --// - Content-Type: text/x-shellscript; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: attachment; filename="userdata.txt" - - #!/bin/bash - # Install Docker - if ! command -v docker &> /dev/null - then - echo "Docker not found, installing..." - curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh 2>&1 >/dev/null - usermod -aG docker ${local.linux_user} - newgrp docker - else - echo "Docker is already installed." - fi - - # Set up Docker credentials - mkdir -p "/home/${local.linux_user}/.docker" - if [ -n "${local.docker_config_json_base64}" ]; then - # Write the Docker config JSON to disk if it is provided. - printf "%s" "${local.docker_config_json_base64}" | base64 -d | tee "/home/${local.linux_user}/.docker/config.json" - else - # Assume that we're going to use the instance IAM role to pull from the cache repo if we need to. - # Set up the ecr credential helper. - apt-get update -y && apt-get install -y amazon-ecr-credential-helper - mkdir -p .docker - printf '{"credsStore": "ecr-login"}' | tee "/home/${local.linux_user}/.docker/config.json" - fi - chown -R ${local.linux_user}:${local.linux_user} "/home/${local.linux_user}/.docker" - - # Write the container env to disk. - printf "%s" "${local.docker_env_list_base64}" | base64 -d | tee "/home/${local.linux_user}/env.txt" - - # Start envbuilder - sudo -u coder docker run \ - --rm \ - --net=host \ - -h ${lower(data.coder_workspace.me.name)} \ - -v /home/${local.linux_user}/envbuilder:/workspaces \ - -v /var/run/docker.sock:/var/run/docker.sock \ - --env-file /home/${local.linux_user}/env.txt \ - ${local.builder_image} - --//-- - EOT } # Check for the presence of a prebuilt image in the cache repo @@ -274,9 +199,48 @@ resource "envbuilder_cached_image" "cached" { extra_env = local.envbuilder_env } +data "cloudinit_config" "user_data" { + gzip = false + base64_encode = false + + boundary = "//" + + part { + filename = "cloud-config.yaml" + content_type = "text/cloud-config" + + content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", { + hostname = local.hostname + linux_user = local.linux_user + + ssh_pubkey = data.coder_parameter.ssh_pubkey.value + }) + } + + part { + filename = "userdata.sh" + content_type = "text/x-shellscript" + + content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", { + hostname = local.hostname + linux_user = local.linux_user + + # If we have a cached image, use the cached image's environment variables. + # Otherwise, just use the environment variables we've defined in locals. + environment = try(envbuilder_cached_image.cached[0].env_map, local.envbuilder_env) + + # Builder image will either be the builder image parameter, or the cached image, if cache is provided. + builder_image = try(envbuilder_cached_image.cached[0].image, data.coder_parameter.devcontainer_builder.value) + + docker_config_json_base64 = local.docker_config_json_base64 + }) + } +} + +# TODO: replace this with the new thing # This is useful for debugging the startup script. Left here for reference. # resource local_file "startup_script" { -# content = local.user_data +# content = data.cloudinit_config.user_data.rendered # filename = "${path.module}/user_data.txt" # } @@ -289,15 +253,18 @@ resource "aws_instance" "vm" { volume_size = data.coder_parameter.root_volume_size_gb.value } - user_data = local.user_data + user_data = data.cloudinit_config.user_data.rendered tags = { - Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}" + Name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" # Required if you are using our example policy, see template README Coder_Provisioned = "true" } lifecycle { ignore_changes = [ami] } + + # TODO: remove + key_name = "phorcys-roflpad" } resource "aws_ec2_instance_state" "vm" { @@ -348,6 +315,11 @@ resource "coder_metadata" "info" { key = "ssh_pubkey" value = data.coder_parameter.ssh_pubkey.value } + # TODO: remove + item { + key = "cloudinit_config" + value = base64encode(data.cloudinit_config.user_data.rendered) + } item { key = "repo_url" value = data.coder_parameter.repo_url.value From d708b905b2ef3d247ae211a4c1992219a840cdba Mon Sep 17 00:00:00 2001 From: Phorcys <57866459+phorcys420@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:05:19 +0000 Subject: [PATCH 2/4] chore(aws-devcontainer): remove debug code --- examples/templates/aws-devcontainer/main.tf | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/templates/aws-devcontainer/main.tf b/examples/templates/aws-devcontainer/main.tf index 7d0decfef6a61..d8ed2803d83fb 100644 --- a/examples/templates/aws-devcontainer/main.tf +++ b/examples/templates/aws-devcontainer/main.tf @@ -237,7 +237,6 @@ data "cloudinit_config" "user_data" { } } -# TODO: replace this with the new thing # This is useful for debugging the startup script. Left here for reference. # resource local_file "startup_script" { # content = data.cloudinit_config.user_data.rendered @@ -262,9 +261,6 @@ resource "aws_instance" "vm" { lifecycle { ignore_changes = [ami] } - - # TODO: remove - key_name = "phorcys-roflpad" } resource "aws_ec2_instance_state" "vm" { @@ -315,11 +311,6 @@ resource "coder_metadata" "info" { key = "ssh_pubkey" value = data.coder_parameter.ssh_pubkey.value } - # TODO: remove - item { - key = "cloudinit_config" - value = base64encode(data.cloudinit_config.user_data.rendered) - } item { key = "repo_url" value = data.coder_parameter.repo_url.value From e0801310f9cce87db11f5e28dc684b6c8c101aa4 Mon Sep 17 00:00:00 2001 From: Phorcys <57866459+phorcys420@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:15:29 +0000 Subject: [PATCH 3/4] chore(aws-devcontainer): format --- examples/templates/aws-devcontainer/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/templates/aws-devcontainer/main.tf b/examples/templates/aws-devcontainer/main.tf index d8ed2803d83fb..a8f6a2bbd4b46 100644 --- a/examples/templates/aws-devcontainer/main.tf +++ b/examples/templates/aws-devcontainer/main.tf @@ -210,7 +210,7 @@ data "cloudinit_config" "user_data" { content_type = "text/cloud-config" content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", { - hostname = local.hostname + hostname = local.hostname linux_user = local.linux_user ssh_pubkey = data.coder_parameter.ssh_pubkey.value @@ -222,7 +222,7 @@ data "cloudinit_config" "user_data" { content_type = "text/x-shellscript" content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", { - hostname = local.hostname + hostname = local.hostname linux_user = local.linux_user # If we have a cached image, use the cached image's environment variables. From 531ac5fcae7574ba940953fd69c04daeabd5b07b Mon Sep 17 00:00:00 2001 From: Phorcys <57866459+phorcys420@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:13:04 +0000 Subject: [PATCH 4/4] chore: remove comment --- examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl b/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl index ddadc2b62ad6c..67c166cb6c164 100644 --- a/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl +++ b/examples/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl @@ -13,7 +13,6 @@ fi # Set up Docker credentials mkdir -p "/home/${linux_user}/.docker" -# NOTE: do we want to use the templating engine's ifs instead? if [ -n "${docker_config_json_base64}" ]; then # Write the Docker config JSON to disk if it is provided. printf "%s" "${docker_config_json_base64}" | base64 -d | tee "/home/${linux_user}/.docker/config.json"
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: