@@ -6,6 +6,9 @@ terraform {
6
6
aws = {
7
7
source = " hashicorp/aws"
8
8
}
9
+ envbuilder = {
10
+ source = " coder/envbuilder"
11
+ }
9
12
}
10
13
}
11
14
@@ -14,6 +17,45 @@ module "aws_region" {
14
17
default = " us-east-1"
15
18
}
16
19
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
+
17
59
data "coder_parameter" "instance_type" {
18
60
name = " instance_type"
19
61
display_name = " Instance type"
@@ -46,25 +88,26 @@ data "coder_parameter" "instance_type" {
46
88
}
47
89
}
48
90
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
54
98
}
55
- data "coder_workspace_owner" "me" {}
56
99
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
68
111
}
69
112
70
113
data "coder_parameter" "repo_url" {
@@ -75,39 +118,66 @@ data "coder_parameter" "repo_url" {
75
118
mutable = true
76
119
}
77
120
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
+ }
85
128
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
100
132
}
101
133
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
106
137
}
107
138
139
+ # Be careful when modifying the below locals!
108
140
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
111
181
Content-Type: multipart/mixed; boundary="//"
112
182
MIME-Version: 1.0
113
183
@@ -125,6 +195,8 @@ locals {
125
195
- name: ${ local . linux_user }
126
196
sudo: ALL=(ALL) NOPASSWD:ALL
127
197
shell: /bin/bash
198
+ ssh_authorized_keys:
199
+ - "${ data . coder_parameter . ssh_pubkey . value } "
128
200
129
201
--//
130
202
Content-Type: text/x-shellscript; charset="us-ascii"
@@ -144,24 +216,57 @@ locals {
144
216
echo "Docker is already installed."
145
217
fi
146
218
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
+
147
236
# Start envbuilder
148
- docker run --rm \
237
+ sudo -u coder docker run \
238
+ --rm \
239
+ --net=host \
149
240
-h ${ lower (data. coder_workspace . me . name )} \
150
241
-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 }
157
245
--//--
158
246
EOT
159
247
}
160
248
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
+
161
265
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 )
165
270
root_block_device {
166
271
volume_size = 30
167
272
}
@@ -181,3 +286,62 @@ resource "aws_ec2_instance_state" "vm" {
181
286
instance_id = aws_instance. vm . id
182
287
state = data. coder_workspace . me . transition == " start" ? " running" : " stopped"
183
288
}
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