From e69cbb1a050a0a274357c7c203e88417afd67bfb Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 28 Jun 2024 10:07:09 +0100 Subject: [PATCH 1/2] chore: add envbuilder-dogfood template --- envbuilder-dogfood/README.md | 16 ++ envbuilder-dogfood/main.tf | 417 +++++++++++++++++++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 envbuilder-dogfood/README.md create mode 100644 envbuilder-dogfood/main.tf diff --git a/envbuilder-dogfood/README.md b/envbuilder-dogfood/README.md new file mode 100644 index 0000000000000..d140820a8e61e --- /dev/null +++ b/envbuilder-dogfood/README.md @@ -0,0 +1,16 @@ +# envbuilder dogfood template + +This template uses the same image as the [dogfood](../dogfood) template, but +builds it on-demand using the latest _preview_ version of [envbuilder](https://github.com/coder/envbuilder). + +In theory, it should work with any Git repository containing a `devcontainer.json`. +The Git repository specified by `devcontainer_repo` is cloned into `/workspaces` upon startup and the container is built from the devcontainer located under the path specified by `devcontainer_dir`. +The `region` parameters are the same as for the [dogfood](../dogfood) template. + +The `/workspaces` directory is persisted as a Docker volume, so any changes you make to the dogfood Dockerfile or devcontainer.json will be applied upon restarting your workspace. + +## Personalization + +The startup script runs your `~/personalize` file if it exists. +You also have a persistent home directory under `/home/coder`. + diff --git a/envbuilder-dogfood/main.tf b/envbuilder-dogfood/main.tf new file mode 100644 index 0000000000000..63c8c9b4a3b5d --- /dev/null +++ b/envbuilder-dogfood/main.tf @@ -0,0 +1,417 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0.0" + } + } +} + +locals { + // These are cluster service addresses mapped to Tailscale nodes. + // Ask #dogfood-admins for help. + // NOTE: keep these up to date with those in ../dogfood/main.tf! + docker_host = { + "" = "tcp://dogfood-ts-cdr-dev.tailscale.svc.cluster.local:2375" + "us-pittsburgh" = "tcp://dogfood-ts-cdr-dev.tailscale.svc.cluster.local:2375" + "eu-helsinki" = "tcp://reinhard-hel-cdr-dev.tailscale.svc.cluster.local:2375" + "ap-sydney" = "tcp://wolfgang-syd-cdr-dev.tailscale.svc.cluster.local:2375" + "sa-saopaulo" = "tcp://oberstein-sao-cdr-dev.tailscale.svc.cluster.local:2375" + "za-jnb" = "tcp://greenhill-jnb-cdr-dev.tailscale.svc.cluster.local:2375" + } + + envbuilder_repo = "ghcr.io/coder/envbuilder-preview" + container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + // Envbuilder clones repos to /workspaces by default. + repo_dir = "/workspaces/coder" +} + +data "coder_parameter" "devcontainer_repo" { + type = "string" + name = "Devcontainer Repository" + default = "https://github.com/coder/coder" + description = "Repo containing a devcontainer.json. This is only cloned once." + mutable = false +} + +data "coder_parameter" "devcontainer_dir" { + type = "string" + name = "Devcontainer Directory" + default = "dogfood/" + description = "Directory containing a devcontainer.json relative to the repository root" + mutable = true +} + +data "coder_parameter" "region" { + type = "string" + name = "Region" + icon = "/emojis/1f30e.png" + default = "us-pittsburgh" + option { + icon = "/emojis/1f1fa-1f1f8.png" + name = "Pittsburgh" + value = "us-pittsburgh" + } + option { + icon = "/emojis/1f1eb-1f1ee.png" + name = "Helsinki" + value = "eu-helsinki" + } + option { + icon = "/emojis/1f1e6-1f1fa.png" + name = "Sydney" + value = "ap-sydney" + } + option { + icon = "/emojis/1f1e7-1f1f7.png" + name = "São Paulo" + value = "sa-saopaulo" + } + option { + icon = "/emojis/1f1ff-1f1e6.png" + name = "Johannesburg" + value = "za-jnb" + } +} + +provider "docker" { + host = lookup(local.docker_host, data.coder_parameter.region.value) +} + +provider "coder" {} + +data "coder_external_auth" "github" { + id = "github" +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +module "slackme" { + source = "registry.coder.com/modules/slackme/coder" + version = "1.0.2" + agent_id = coder_agent.dev.id + auth_provider_id = "slack" +} + +module "dotfiles" { + source = "registry.coder.com/modules/dotfiles/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id +} + +module "personalize" { + source = "registry.coder.com/modules/personalize/coder" + version = "1.0.2" + agent_id = coder_agent.dev.id +} + +module "code-server" { + source = "registry.coder.com/modules/code-server/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id + folder = local.repo_dir + auto_install_extensions = true +} + +module "jetbrains_gateway" { + source = "registry.coder.com/modules/jetbrains-gateway/coder" + version = "1.0.13" + agent_id = coder_agent.dev.id + agent_name = "dev" + folder = local.repo_dir + jetbrains_ides = ["GO", "WS"] + default = "GO" + latest = true +} + +module "filebrowser" { + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.8" + agent_id = coder_agent.dev.id +} + +module "coder-login" { + source = "registry.coder.com/modules/coder-login/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id +} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = local.repo_dir + env = { + OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, + } + startup_script_behavior = "blocking" + + # The following metadata blocks are optional. They are used to display + # information about your workspace in the dashboard. You can remove them + # if you don't want to display any information. + metadata { + display_name = "CPU Usage" + key = "cpu_usage" + order = 0 + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "ram_usage" + order = 1 + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "CPU Usage (Host)" + key = "cpu_usage_host" + order = 2 + script = "coder stat cpu --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage (Host)" + key = "ram_usage_host" + order = 3 + script = "coder stat mem --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Swap Usage (Host)" + key = "swap_usage_host" + order = 4 + script = <&1 | awk ' $0 ~ "Word of the Day: [A-z]+" { print $5; exit }' + EOT + interval = 86400 + timeout = 5 + } + + startup_script = <<-EOT + set -eux -o pipefail + + # Allow synchronization between scripts. + trap 'touch /tmp/.coder-startup-script.done' EXIT + + # BUG: Kaniko does not symlink /run => /var/run properly, resulting in + # /var/run/ owned by root:root + # WORKAROUND: symlink it manually + sudo ln -s /run /var/run + # Start Docker service + sudo service docker start + + # Chown /var/run/docker.sock as even though we are a member of the Docker group + # it did not exist at the start of the workspace. This can be worked around with + # `newgrp docker` but this is annoying to have to do manually. + for attempt in $(seq 1 10); do + if sudo docker info > /dev/null; then break; fi + sleep 1 + done + sudo chmod a+rw /var/run/docker.sock + + # Install playwright dependencies + # We want to use the playwright version from site/package.json + # Check if the directory exists At workspace creation as the coder_script runs in parallel so clone might not exist yet. + while ! [[ -f "${local.repo_dir}/site/package.json" ]]; do + sleep 1 + done + cd "${local.repo_dir}/site" && pnpm install && pnpm playwright:install + EOT +} + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + # Protect the volume from being deleted due to changes in attributes. + lifecycle { + ignore_changes = all + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + # This field becomes outdated if the workspace is renamed but can + # be useful for debugging or cleaning out dangling volumes. + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } +} + +resource "docker_volume" "workspaces" { + name = "coder-${data.coder_workspace.me.id}" + # Protect the volume from being deleted due to changes in attributes. + lifecycle { + ignore_changes = all + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + # This field becomes outdated if the workspace is renamed but can + # be useful for debugging or cleaning out dangling volumes. + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } +} + +# This file is mounted as a Kubernetes secret on provisioner pods. +# It contains the required credentials for the envbuilder cache repo. +data "local_sensitive_file" "envbuilder_cache_dockerconfigjson" { + filename = "/home/coder/envbuilder-cache-dockerconfig.json" +} + +data "docker_registry_image" "envbuilder" { + name = "${local.envbuilder_repo}:latest" +} + +resource "docker_image" "envbuilder" { + name = "${local.envbuilder_repo}@${data.docker_registry_image.envbuilder.sha256_digest}" + pull_triggers = [data.docker_registry_image.envbuilder.sha256_digest] + keep_locally = true +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = docker_image.envbuilder.name + name = local.container_name + # Hostname makes the shell more user friendly: coder@my-workspace:~$ + hostname = data.coder_workspace.me.name + # CPU limits are unnecessary since Docker will load balance automatically + memory = 32768 + runtime = "sysbox-runc" + env = [ + "CODER_AGENT_TOKEN=${coder_agent.dev.token}", + "CODER_AGENT_URL=${data.coder_workspace.me.access_url}", + "ENVBUILDER_GIT_USERNAME=${data.coder_external_auth.github.access_token}", + "ENVBUILDER_GIT_URL=${data.coder_parameter.devcontainer_repo.value}", + "ENVBUILDER_DEVCONTAINER_DIR=${data.coder_parameter.devcontainer_dir.value}", + "ENVBUILDER_INIT_SCRIPT=${coder_agent.dev.init_script}", + "ENVBUILDER_FALLBACK_IMAGE=codercom/oss-dogfood:latest", # This image runs if builds fail + # "ENVBUILDER_PUSH_IMAGE=1", # Push the image to the remote cache + "ENVBUILDER_CACHE_REPO=us-central1-docker.pkg.dev/coder-dogfood-v2/envbuilder-cache/coder-dogfood", + "ENVBUILDER_DOCKER_CONFIG_BASE64=${data.local_sensitive_file.envbuilder_cache_dockerconfigjson.content_base64}", + "USE_CAP_NET_ADMIN=true", + # Set git commit details correctly + "GIT_AUTHOR_NAME=${coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)}", + "GIT_AUTHOR_EMAIL=${data.coder_workspace_owner.me.email}", + "GIT_COMMITTER_NAME=${coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)}", + "GIT_COMMITTER_EMAIL=${data.coder_workspace_owner.me.email}", + ] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + volumes { + container_path = "/home/coder/" + volume_name = docker_volume.home_volume.name + read_only = false + } + volumes { + container_path = local.repo_dir + volume_name = docker_volume.workspaces.name + read_only = false + } + capabilities { + add = ["CAP_NET_ADMIN", "CAP_SYS_NICE"] + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } +} + +resource "coder_metadata" "container_info" { + count = data.coder_workspace.me.start_count + resource_id = docker_container.workspace[0].id + item { + key = "memory" + value = docker_container.workspace[0].memory + } + item { + key = "runtime" + value = docker_container.workspace[0].runtime + } + item { + key = "region" + value = data.coder_parameter.region.option[index(data.coder_parameter.region.option.*.value, data.coder_parameter.region.value)].name + } +} From a5bdbfe730aaafc28f5e2c339cde88307c5e5fc2 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 28 Jun 2024 11:14:34 +0100 Subject: [PATCH 2/2] make fmt --- envbuilder-dogfood/README.md | 1 - envbuilder-dogfood/main.tf | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/envbuilder-dogfood/README.md b/envbuilder-dogfood/README.md index d140820a8e61e..568fdd4fe77e7 100644 --- a/envbuilder-dogfood/README.md +++ b/envbuilder-dogfood/README.md @@ -13,4 +13,3 @@ The `/workspaces` directory is persisted as a Docker volume, so any changes you The startup script runs your `~/personalize` file if it exists. You also have a persistent home directory under `/home/coder`. - diff --git a/envbuilder-dogfood/main.tf b/envbuilder-dogfood/main.tf index 63c8c9b4a3b5d..97134b2b8bb6e 100644 --- a/envbuilder-dogfood/main.tf +++ b/envbuilder-dogfood/main.tf @@ -24,7 +24,7 @@ locals { } envbuilder_repo = "ghcr.io/coder/envbuilder-preview" - container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" // Envbuilder clones repos to /workspaces by default. repo_dir = "/workspaces/coder" } @@ -322,7 +322,7 @@ resource "docker_volume" "workspaces" { # This file is mounted as a Kubernetes secret on provisioner pods. # It contains the required credentials for the envbuilder cache repo. -data "local_sensitive_file" "envbuilder_cache_dockerconfigjson" { +data "local_sensitive_file" "envbuilder_cache_dockerconfigjson" { filename = "/home/coder/envbuilder-cache-dockerconfig.json" } @@ -331,9 +331,9 @@ data "docker_registry_image" "envbuilder" { } resource "docker_image" "envbuilder" { - name = "${local.envbuilder_repo}@${data.docker_registry_image.envbuilder.sha256_digest}" + name = "${local.envbuilder_repo}@${data.docker_registry_image.envbuilder.sha256_digest}" pull_triggers = [data.docker_registry_image.envbuilder.sha256_digest] - keep_locally = true + keep_locally = true } resource "docker_container" "workspace" { @@ -343,7 +343,7 @@ resource "docker_container" "workspace" { # Hostname makes the shell more user friendly: coder@my-workspace:~$ hostname = data.coder_workspace.me.name # CPU limits are unnecessary since Docker will load balance automatically - memory = 32768 + memory = 32768 runtime = "sysbox-runc" env = [ "CODER_AGENT_TOKEN=${coder_agent.dev.token}", @@ -362,7 +362,7 @@ resource "docker_container" "workspace" { "GIT_AUTHOR_EMAIL=${data.coder_workspace_owner.me.email}", "GIT_COMMITTER_NAME=${coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)}", "GIT_COMMITTER_EMAIL=${data.coder_workspace_owner.me.email}", - ] + ] host { host = "host.docker.internal" ip = "host-gateway"