Skip to content

Commit 8c8a109

Browse files
committed
chore(examples/templates/aws-devcontainer): update to use envbuilder provider
1 parent 5366f25 commit 8c8a109

File tree

2 files changed

+233
-54
lines changed

2 files changed

+233
-54
lines changed

examples/templates/aws-devcontainer/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ Coder uses `aws_ec2_instance_state` to start and stop the VM. This example templ
8989
> **Note**
9090
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
9191
92+
## Caching
93+
94+
To speed up your builds, you can use a container registry as a cache.
95+
When creating the template, set the parameter `cache_repo` to a valid Docker repository in the form `host.tld/path/to/repo`.
96+
97+
See the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.
98+
99+
> [!NOTE] We recommend using a registry cache with authentication enabled.
100+
> To allow Envbuilder to authenticate with a registry cache hosted on ECR, specify an IAM instance
101+
> profile that has read and write access to the given registry. For more information, see the
102+
> [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html).
103+
>
104+
> Alternatively, you can specify the variable `cache_repo_docker_config_path`
105+
> with the path to a Docker config `.json` on disk containing valid credentials for the registry.
106+
92107
## code-server
93108

94109
`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. For a list of all modules and templates pplease check [Coder Registry](https://registry.coder.com).

examples/templates/aws-devcontainer/main.tf

Lines changed: 218 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ terraform {
66
aws = {
77
source = "hashicorp/aws"
88
}
9+
envbuilder = {
10+
source = "coder/envbuilder"
11+
}
912
}
1013
}
1114

@@ -14,6 +17,45 @@ module "aws_region" {
1417
default = "us-east-1"
1518
}
1619

20+
provider "aws" {
21+
region = module.aws_region.value
22+
}
23+
24+
variable "cache_repo" {
25+
default = ""
26+
description = "(Optional) Use a container registry as a cache to speed up builds. Example: host.tld/path/to/repo."
27+
type = string
28+
}
29+
30+
variable "cache_repo_docker_config_path" {
31+
default = ""
32+
description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required. This will depend on your Coder setup. Example: `/home/coder/.docker/config.json`."
33+
sensitive = true
34+
type = string
35+
}
36+
37+
variable "iam_instance_profile" {
38+
default = ""
39+
description = "(Optional) Name of an IAM instance profile to assign to the instance."
40+
type = string
41+
}
42+
43+
data "coder_workspace" "me" {}
44+
data "coder_workspace_owner" "me" {}
45+
46+
data "aws_ami" "ubuntu" {
47+
most_recent = true
48+
filter {
49+
name = "name"
50+
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
51+
}
52+
filter {
53+
name = "virtualization-type"
54+
values = ["hvm"]
55+
}
56+
owners = ["099720109477"] # Canonical
57+
}
58+
1759
data "coder_parameter" "instance_type" {
1860
name = "instance_type"
1961
display_name = "Instance type"
@@ -46,25 +88,26 @@ data "coder_parameter" "instance_type" {
4688
}
4789
}
4890

49-
provider "aws" {
50-
region = module.aws_region.value
51-
}
52-
53-
data "coder_workspace" "me" {
91+
data "coder_parameter" "fallback_image" {
92+
default = "codercom/enterprise-base:ubuntu"
93+
description = "This image runs if the devcontainer fails to build."
94+
display_name = "Fallback Image"
95+
mutable = true
96+
name = "fallback_image"
97+
order = 3
5498
}
55-
data "coder_workspace_owner" "me" {}
5699

57-
data "aws_ami" "ubuntu" {
58-
most_recent = true
59-
filter {
60-
name = "name"
61-
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
62-
}
63-
filter {
64-
name = "virtualization-type"
65-
values = ["hvm"]
66-
}
67-
owners = ["099720109477"] # Canonical
100+
data "coder_parameter" "devcontainer_builder" {
101+
description = <<-EOF
102+
Image that will build the devcontainer.
103+
Find the latest version of Envbuilder here: https://ghcr.io/coder/envbuilder
104+
Be aware that using the `:latest` tag may expose you to breaking changes.
105+
EOF
106+
display_name = "Devcontainer Builder"
107+
mutable = true
108+
name = "devcontainer_builder"
109+
default = "ghcr.io/coder/envbuilder:latest"
110+
order = 4
68111
}
69112

70113
data "coder_parameter" "repo_url" {
@@ -75,39 +118,66 @@ data "coder_parameter" "repo_url" {
75118
mutable = true
76119
}
77120

78-
resource "coder_agent" "dev" {
79-
count = data.coder_workspace.me.start_count
80-
arch = "amd64"
81-
auth = "token"
82-
os = "linux"
83-
dir = "/workspaces/${trimsuffix(basename(data.coder_parameter.repo_url.value), ".git")}"
84-
connection_timeout = 0
121+
data "coder_parameter" "ssh_pubkey" {
122+
name = "ssh_pubkey"
123+
display_name = "SSH Public Key"
124+
default = ""
125+
description = "(Optional) Add an SSH public key to the `coder` user's authorized_keys. Useful for troubleshooting. You may need to add a security group to the instance."
126+
mutable = false
127+
}
85128

86-
metadata {
87-
key = "cpu"
88-
display_name = "CPU Usage"
89-
interval = 5
90-
timeout = 5
91-
script = "coder stat cpu"
92-
}
93-
metadata {
94-
key = "memory"
95-
display_name = "Memory Usage"
96-
interval = 5
97-
timeout = 5
98-
script = "coder stat mem"
99-
}
129+
data "local_sensitive_file" "cache_repo_dockerconfigjson" {
130+
count = var.cache_repo_docker_config_path == "" ? 0 : 1
131+
filename = var.cache_repo_docker_config_path
100132
}
101133

102-
module "code-server" {
103-
count = data.coder_workspace.me.start_count
104-
source = "https://registry.coder.com/modules/code-server"
105-
agent_id = coder_agent.dev[0].id
134+
data "aws_iam_instance_profile" "vm_instance_profile" {
135+
count = var.iam_instance_profile == "" ? 0 : 1
136+
name = var.iam_instance_profile
106137
}
107138

139+
# Be careful when modifying the below locals!
108140
locals {
109-
linux_user = "coder"
110-
user_data = <<-EOT
141+
# TODO: provide a way to pick the availability zone.
142+
aws_availability_zone = "${module.aws_region.value}a"
143+
linux_user = "coder"
144+
# Name the container after the workspace and owner.
145+
container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
146+
# The devcontainer builder image is the image that will build the devcontainer.
147+
devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
148+
# We may need to authenticate with a registry. If so, the user will provide a path to a docker config.json.
149+
docker_config_json_base64 = try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, "")
150+
# The envbuilder provider requires a key-value map of environment variables. Build this here.
151+
envbuilder_env = {
152+
# ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
153+
# if the cache repo is enabled.
154+
"ENVBUILDER_GIT_URL" : data.coder_parameter.repo_url.value,
155+
# The agent token is required for the agent to connect to the Coder platform.
156+
"CODER_AGENT_TOKEN" : try(coder_agent.dev.0.token, ""),
157+
# The agent URL is required for the agent to connect to the Coder platform.
158+
"CODER_AGENT_URL" : data.coder_workspace.me.access_url,
159+
# The agent init script is required for the agent to start up. We base64 encode it here
160+
# to avoid quoting issues.
161+
"ENVBUILDER_INIT_SCRIPT" : "echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh",
162+
"ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""),
163+
# The fallback image is the image that will run if the devcontainer fails to build.
164+
"ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
165+
# The following are used to push the image to the cache repo, if defined.
166+
"ENVBUILDER_CACHE_REPO" : var.cache_repo,
167+
"ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
168+
# You can add other required environment variables here.
169+
# See: https://github.com/coder/envbuilder/?tab=readme-ov-file#environment-variables
170+
}
171+
# If we have a cached image, use the cached image's environment variables. Otherwise, just use
172+
# the environment variables we've defined above.
173+
docker_env_input = try(envbuilder_cached_image.cached.0.env_map, local.envbuilder_env)
174+
# Convert the above to the list of arguments for the Docker run command.
175+
# The startup script will write this to a file, which the Docker run command will reference.
176+
docker_env_list_base64 = base64encode(join("\n", [for k, v in local.docker_env_input : "${k}=${v}"]))
177+
# Builder image will either be the builder image parameter, or the cached image, if cache is provided.
178+
builder_image = try(envbuilder_cached_image.cached[0].image, data.coder_parameter.devcontainer_builder.value)
179+
# User data to start the workspace.
180+
user_data = <<-EOT
111181
Content-Type: multipart/mixed; boundary="//"
112182
MIME-Version: 1.0
113183
@@ -125,6 +195,8 @@ locals {
125195
- name: ${local.linux_user}
126196
sudo: ALL=(ALL) NOPASSWD:ALL
127197
shell: /bin/bash
198+
ssh_authorized_keys:
199+
- "${data.coder_parameter.ssh_pubkey.value}"
128200
129201
--//
130202
Content-Type: text/x-shellscript; charset="us-ascii"
@@ -144,24 +216,57 @@ locals {
144216
echo "Docker is already installed."
145217
fi
146218
219+
# Set up Docker credentials
220+
mkdir -p "/home/${local.linux_user}/.docker"
221+
if [ -n "${local.docker_config_json_base64}" ]; then
222+
# Write the Docker config JSON to disk if it is provided.
223+
printf "%s" "${local.docker_config_json_base64}" | base64 -d | tee "/home/${local.linux_user}/.docker/config.json"
224+
else
225+
# Assume that we're going to use the instance IAM role to pull from the cache repo if we need to.
226+
# Set up the ecr credential helper.
227+
apt-get update -y && apt-get install -y amazon-ecr-credential-helper
228+
mkdir -p .docker
229+
printf '{"credsStore": "ecr-login"}' | tee "/home/${local.linux_user}/.docker/config.json"
230+
fi
231+
chown -R ${local.linux_user}:${local.linux_user} "/home/${local.linux_user}/.docker"
232+
233+
# Write the container env to disk.
234+
printf "%s" "${local.docker_env_list_base64}" | base64 -d | tee "/home/${local.linux_user}/env.txt"
235+
147236
# Start envbuilder
148-
docker run --rm \
237+
sudo -u coder docker run \
238+
--rm \
239+
--net=host \
149240
-h ${lower(data.coder_workspace.me.name)} \
150241
-v /home/${local.linux_user}/envbuilder:/workspaces \
151-
-e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \
152-
-e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \
153-
-e GIT_URL="${data.coder_parameter.repo_url.value}" \
154-
-e INIT_SCRIPT="echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh" \
155-
-e FALLBACK_IMAGE="codercom/enterprise-base:ubuntu" \
156-
ghcr.io/coder/envbuilder
242+
-v /var/run/docker.sock:/var/run/docker.sock \
243+
--env-file /home/${local.linux_user}/env.txt \
244+
${local.builder_image}
157245
--//--
158246
EOT
159247
}
160248

249+
# Check for the presence of a prebuilt image in the cache repo
250+
# that we can use instead.
251+
resource "envbuilder_cached_image" "cached" {
252+
count = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
253+
builder_image = local.devcontainer_builder_image
254+
git_url = data.coder_parameter.repo_url.value
255+
cache_repo = var.cache_repo
256+
extra_env = local.envbuilder_env
257+
}
258+
259+
# This is useful for debugging the startup script. Left here for reference.
260+
# resource local_file "startup_script" {
261+
# content = local.user_data
262+
# filename = "${path.module}/user_data.txt"
263+
# }
264+
161265
resource "aws_instance" "vm" {
162-
ami = data.aws_ami.ubuntu.id
163-
availability_zone = "${module.aws_region.value}a"
164-
instance_type = data.coder_parameter.instance_type.value
266+
ami = data.aws_ami.ubuntu.id
267+
availability_zone = local.aws_availability_zone
268+
instance_type = data.coder_parameter.instance_type.value
269+
iam_instance_profile = try(data.aws_iam_instance_profile.vm_instance_profile[0].name, null)
165270
root_block_device {
166271
volume_size = 30
167272
}
@@ -181,3 +286,62 @@ resource "aws_ec2_instance_state" "vm" {
181286
instance_id = aws_instance.vm.id
182287
state = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
183288
}
289+
290+
resource "coder_agent" "dev" {
291+
count = data.coder_workspace.me.start_count
292+
arch = "amd64"
293+
auth = "token"
294+
os = "linux"
295+
dir = "/workspaces/${trimsuffix(basename(data.coder_parameter.repo_url.value), ".git")}"
296+
connection_timeout = 0
297+
298+
metadata {
299+
key = "cpu"
300+
display_name = "CPU Usage"
301+
interval = 5
302+
timeout = 5
303+
script = "coder stat cpu"
304+
}
305+
metadata {
306+
key = "memory"
307+
display_name = "Memory Usage"
308+
interval = 5
309+
timeout = 5
310+
script = "coder stat mem"
311+
}
312+
}
313+
314+
resource "coder_metadata" "info" {
315+
count = data.coder_workspace.me.start_count
316+
resource_id = coder_agent.dev[0].id
317+
item {
318+
key = "ami"
319+
value = aws_instance.vm.ami
320+
}
321+
item {
322+
key = "availability_zone"
323+
value = local.aws_availability_zone
324+
}
325+
item {
326+
key = "instance_type"
327+
value = data.coder_parameter.instance_type.value
328+
}
329+
item {
330+
key = "ssh_pubkey"
331+
value = data.coder_parameter.ssh_pubkey.value
332+
}
333+
item {
334+
key = "repo_url"
335+
value = data.coder_parameter.repo_url.value
336+
}
337+
item {
338+
key = "devcontainer_builder"
339+
value = data.coder_parameter.devcontainer_builder.value
340+
}
341+
}
342+
343+
module "code-server" {
344+
count = data.coder_workspace.me.start_count
345+
source = "https://registry.coder.com/modules/code-server"
346+
agent_id = coder_agent.dev[0].id
347+
}

0 commit comments

Comments
 (0)