From 5b071f4d941906a935559715989f4384818fb5a5 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 21 Dec 2023 16:01:10 +0300 Subject: [PATCH] feat(examples/templates): add GCP VM devcontainer template (#11246) --- .../coder_templates_init_--help.golden | 2 +- docs/cli/templates_init.md | 6 +- examples/examples.gen.json | 29 +++ examples/examples.go | 2 + examples/templates/aws-devcontainer/main.tf | 1 + examples/templates/gcp-devcontainer/README.md | 64 ++++++ .../gcp-devcontainer/architecture.svg | 8 + examples/templates/gcp-devcontainer/main.tf | 207 ++++++++++++++++++ 8 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 examples/templates/gcp-devcontainer/README.md create mode 100644 examples/templates/gcp-devcontainer/architecture.svg create mode 100644 examples/templates/gcp-devcontainer/main.tf diff --git a/cli/testdata/coder_templates_init_--help.golden b/cli/testdata/coder_templates_init_--help.golden index 6d32c3a310..9f7289407c 100644 --- a/cli/testdata/coder_templates_init_--help.golden +++ b/cli/testdata/coder_templates_init_--help.golden @@ -6,7 +6,7 @@ USAGE: Get started with a templated template. OPTIONS: - --id aws-linux|aws-windows|azure-linux|do-linux|docker|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|nomad-docker + --id aws-devcontainer|aws-linux|aws-windows|azure-linux|do-linux|docker|gcp-devcontainer|gcp-linux|gcp-vm-container|gcp-windows|kubernetes|nomad-docker Specify a given example template by ID. ——— diff --git a/docs/cli/templates_init.md b/docs/cli/templates_init.md index fb5df7fbfa..d26a8cb857 100644 --- a/docs/cli/templates_init.md +++ b/docs/cli/templates_init.md @@ -14,8 +14,8 @@ coder templates init [flags] [directory] ### --id -| | | -| ---- | -------------------- | ----------- | ----------- | -------- | ------ | --------- | ---------------- | ----------- | ---------- | -------------------- | -| Type | enum[aws-linux | aws-windows | azure-linux | do-linux | docker | gcp-linux | gcp-vm-container | gcp-windows | kubernetes | nomad-docker] | +| | | +| ---- | --------------------------- | --------- | ----------- | ----------- | -------- | ------ | ---------------- | --------- | ---------------- | ----------- | ---------- | -------------------- | +| Type | enum[aws-devcontainer | aws-linux | aws-windows | azure-linux | do-linux | docker | gcp-devcontainer | gcp-linux | gcp-vm-container | gcp-windows | kubernetes | nomad-docker] | Specify a given example template by ID. diff --git a/examples/examples.gen.json b/examples/examples.gen.json index cff675d1ad..d216581c7c 100644 --- a/examples/examples.gen.json +++ b/examples/examples.gen.json @@ -1,5 +1,20 @@ // Code generated by examplegen. DO NOT EDIT. [ + { + "id": "aws-devcontainer", + "url": "", + "name": "AWS EC2 (Devcontainer)", + "description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces", + "icon": "/icon/aws.png", + "tags": [ + "vm", + "linux", + "aws", + "persistent", + "devcontainer" + ], + "markdown": "\n# Remote Development on AWS EC2 VMs using a Devcontainer\n\nProvision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs/v2/latest) with this example template.\n![Architecture Diagram](./architecture.svg)\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"VisualEditor0\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:GetDefaultCreditSpecification\",\n \"ec2:DescribeIamInstanceProfileAssociations\",\n \"ec2:DescribeTags\",\n \"ec2:DescribeInstances\",\n \"ec2:DescribeInstanceTypes\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:DescribeInstanceCreditSpecifications\",\n \"ec2:DescribeImages\",\n \"ec2:ModifyDefaultCreditSpecification\",\n \"ec2:DescribeVolumes\"\n ],\n \"Resource\": \"*\"\n },\n {\n \"Sid\": \"CoderResources\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:DescribeInstanceAttribute\",\n \"ec2:UnmonitorInstances\",\n \"ec2:TerminateInstances\",\n \"ec2:StartInstances\",\n \"ec2:StopInstances\",\n \"ec2:DeleteTags\",\n \"ec2:MonitorInstances\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:ModifyInstanceAttribute\",\n \"ec2:ModifyInstanceCreditSpecification\"\n ],\n \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"aws:ResourceTag/Coder_Provisioned\": \"true\"\n }\n }\n }\n ]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`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).\n" + }, { "id": "aws-linux", "url": "", @@ -65,6 +80,20 @@ ], "markdown": "\n# Remote Development on Docker Containers\n\nProvision Docker containers as [Coder workspaces](https://coder.com/docs/coder-v2/latest) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\nThe VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:\n\n```sh\n# Add coder user to Docker group\nsudo adduser coder docker\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Docker image (built by Docker socket and kept locally)\n- Docker container pod (ephemeral)\n- Docker volume (persistent on `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/v2/latest/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n### Editing the image\n\nEdit the `Dockerfile` and run `coder templates push` to update workspaces.\n" }, + { + "id": "gcp-devcontainer", + "url": "", + "name": "Google Compute Engine (Devcontainer)", + "description": "Provision a Devcontainer on Google Compute Engine instances as Coder workspaces", + "icon": "/icon/gcp.png", + "tags": [ + "vm", + "linux", + "gcp", + "devcontainer" + ], + "markdown": "\n# Remote Development in a Devcontainer on Google Compute Engine\n\n![Architecture Diagram](./architecture.svg)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (persistent)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. Please check [Coder Registry](https://registry.coder.com) for a list of all modules and templates.\n" + }, { "id": "gcp-linux", "url": "", diff --git a/examples/examples.go b/examples/examples.go index 5603f7db26..9672f52781 100644 --- a/examples/examples.go +++ b/examples/examples.go @@ -23,11 +23,13 @@ var ( // Only some templates are embedded that we want to display inside the UI. // The metadata in examples.gen.json is generated via scripts/examplegen. //go:embed examples.gen.json + //go:embed templates/aws-devcontainer //go:embed templates/aws-linux //go:embed templates/aws-windows //go:embed templates/azure-linux //go:embed templates/do-linux //go:embed templates/docker + //go:embed templates/gcp-devcontainer //go:embed templates/gcp-linux //go:embed templates/gcp-vm-container //go:embed templates/gcp-windows diff --git a/examples/templates/aws-devcontainer/main.tf b/examples/templates/aws-devcontainer/main.tf index 74d380723a..664ace2713 100644 --- a/examples/templates/aws-devcontainer/main.tf +++ b/examples/templates/aws-devcontainer/main.tf @@ -145,6 +145,7 @@ locals { # Start envbuilder docker run --rm \ + -h ${lower(data.coder_workspace.me.name)} \ -v /home/${local.linux_user}/envbuilder:/workspaces \ -e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \ -e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \ diff --git a/examples/templates/gcp-devcontainer/README.md b/examples/templates/gcp-devcontainer/README.md new file mode 100644 index 0000000000..bbf33779dd --- /dev/null +++ b/examples/templates/gcp-devcontainer/README.md @@ -0,0 +1,64 @@ +--- +display_name: Google Compute Engine (Devcontainer) +description: Provision a Devcontainer on Google Compute Engine instances as Coder workspaces +icon: ../../../site/static/icon/gcp.png +maintainer_github: coder +verified: true +tags: [vm, linux, gcp, devcontainer] +--- + +# Remote Development in a Devcontainer on Google Compute Engine + +![Architecture Diagram](./architecture.svg) + +## Prerequisites + +### Authentication + +This template assumes that coderd is run in an environment that is authenticated +with Google Cloud. For example, run `gcloud auth application-default login` to +import credentials on the system and user running coderd. For other ways to +authenticate [consult the Terraform +docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials). + +Coder requires a Google Cloud Service Account to provision workspaces. To create +a service account: + +1. Navigate to the [CGP + console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create), + and select your Cloud project (if you have more than one project associated + with your account) + +1. Provide a service account name (this name is used to generate the service + account ID) + +1. Click **Create and continue**, and choose the following IAM roles to grant to + the service account: + + - Compute Admin + - Service Account User + + Click **Continue**. + +1. Click on the created key, and navigate to the **Keys** tab. + +1. Click **Add key** > **Create new key**. + +1. Generate a **JSON private key**, which will be what you provide to Coder + during the setup process. + +## Architecture + +This template provisions the following resources: + +- GCP VM (persistent) +- GCP Disk (persistent, mounted to root) + +Coder persists the root volume. The full filesystem is preserved when the workspace restarts. + +> **Note** +> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case. + +## code-server + +`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. Please check [Coder Registry](https://registry.coder.com) for a list of all modules and templates. diff --git a/examples/templates/gcp-devcontainer/architecture.svg b/examples/templates/gcp-devcontainer/architecture.svg new file mode 100644 index 0000000000..9ef07abbca --- /dev/null +++ b/examples/templates/gcp-devcontainer/architecture.svg @@ -0,0 +1,8 @@ +GCPGCPHostingHostingVirtual MachineVirtual MachineLinux HardwareLinux HardwareCoder WorkspaceCoder WorkspaceDevcontainerDevcontainerenvbuilder created filesytemenvbuilder created filesytemA Clone of your repoA Clone of your repoSource codeSource codeLanguagesLanguagesPython. Go, etcPython. Go, etcToolingToolingExtensions, linting, formatting, etcExtensions, linting, formatting, etcCPUsCPUsDisk StorageDisk StorageCode EditorCode EditorVS Code DesktopVS Code DesktopLocal InstallationLocal InstallationVS Code DesktopVS Code DesktopLocal InstallationLocal Installationcode-servercode-serverA web IDEA web IDEJetBrains GatewayJetBrains GatewayLocal InstallationLocal InstallationCommand LineCommand LineSSH via Coder CLISSH via Coder CLI \ No newline at end of file diff --git a/examples/templates/gcp-devcontainer/main.tf b/examples/templates/gcp-devcontainer/main.tf new file mode 100644 index 0000000000..00323ec20a --- /dev/null +++ b/examples/templates/gcp-devcontainer/main.tf @@ -0,0 +1,207 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + google = { + source = "hashicorp/google" + } + } +} + +provider "coder" { +} + +variable "project_id" { + description = "Which Google Compute Project should your workspace live in?" +} + +data "coder_parameter" "zone" { + name = "zone" + display_name = "Zone" + description = "Which zone should your workspace live in?" + type = "string" + icon = "/emojis/1f30e.png" + default = "us-central1-a" + mutable = false + option { + name = "North America (Northeast)" + value = "northamerica-northeast1-a" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "North America (Central)" + value = "us-central1-a" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "North America (West)" + value = "us-west2-c" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Europe (West)" + value = "europe-west4-b" + icon = "/emojis/1f1ea-1f1fa.png" + } + option { + name = "South America (East)" + value = "southamerica-east1-a" + icon = "/emojis/1f1e7-1f1f7.png" + } +} + +provider "google" { + zone = data.coder_parameter.zone.value + project = var.project_id +} + +data "google_compute_default_service_account" "default" { +} + +data "coder_workspace" "me" { +} + +resource "google_compute_disk" "root" { + name = "coder-${data.coder_workspace.me.id}-root" + type = "pd-ssd" + image = "debian-cloud/debian-12" + lifecycle { + ignore_changes = [name, image] + } +} + +data "coder_parameter" "repo_url" { + name = "repo_url" + display_name = "Repository URL" + default = "https://github.com/coder/envbuilder-starter-devcontainer" + description = "Repository URL" + mutable = true +} + +resource "coder_agent" "dev" { + count = data.coder_workspace.me.start_count + arch = "amd64" + auth = "token" + os = "linux" + dir = "/workspaces/${trimsuffix(basename(data.coder_parameter.repo_url.value), ".git")}" + connection_timeout = 0 + + metadata { + key = "cpu" + display_name = "CPU Usage" + interval = 5 + timeout = 5 + script = "coder stat cpu" + } + metadata { + key = "memory" + display_name = "Memory Usage" + interval = 5 + timeout = 5 + script = "coder stat mem" + } + metadata { + key = "disk" + display_name = "Disk Usage" + interval = 5 + timeout = 5 + script = "coder stat disk" + } +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "https://registry.coder.com/modules/code-server" + agent_id = coder_agent.dev[0].id +} + +resource "google_compute_instance" "vm" { + name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}-root" + machine_type = "e2-medium" + # data.coder_workspace.me.owner == "default" is a workaround to suppress error in the terraform plan phase while creating a new workspace. + desired_status = (data.coder_workspace.me.owner == "default" || data.coder_workspace.me.start_count == 1) ? "RUNNING" : "TERMINATED" + + network_interface { + network = "default" + access_config { + // Ephemeral public IP + } + } + + boot_disk { + auto_delete = false + source = google_compute_disk.root.name + } + + service_account { + email = data.google_compute_default_service_account.default.email + scopes = ["cloud-platform"] + } + + metadata = { + # The startup script runs as root with no $HOME environment set up, so instead of directly + # running the agent init script, create a user (with a homedir, default shell and sudo + # permissions) and execute the init script as that user. + startup-script = <<-META + #!/usr/bin/env sh + set -eux + + # If user does not exist, create it and set up passwordless sudo + if ! id -u "${local.linux_user}" >/dev/null 2>&1; then + useradd -m -s /bin/bash "${local.linux_user}" + echo "${local.linux_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder-user + fi + + # Check for Docker, install if not present + if ! command -v docker &> /dev/null + then + echo "Docker not found, installing..." + curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh 2>&1 >/dev/null + sudo usermod -aG docker ${local.linux_user} + newgrp docker + else + echo "Docker is already installed." + fi + # Start envbuilder + docker run --rm \ + -h ${lower(data.coder_workspace.me.name)} \ + -v /home/${local.linux_user}/envbuilder:/workspaces \ + -e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \ + -e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \ + -e GIT_URL="${data.coder_parameter.repo_url.value}" \ + -e INIT_SCRIPT="echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh" \ + -e FALLBACK_IMAGE="codercom/enterprise-base:ubuntu" \ + ghcr.io/coder/envbuilder + META + } +} + +locals { + # Ensure Coder username is a valid Linux username + linux_user = lower(substr(data.coder_workspace.me.owner, 0, 32)) +} + +resource "coder_metadata" "workspace_info" { + count = data.coder_workspace.me.start_count + resource_id = google_compute_instance.vm.id + + item { + key = "type" + value = google_compute_instance.vm.machine_type + } + + item { + key = "zone" + value = data.coder_parameter.zone.value + } +} + +resource "coder_metadata" "home_info" { + resource_id = google_compute_disk.root.id + + item { + key = "size" + value = "${google_compute_disk.root.size} GiB" + } +}