Add Cloud DevOps workspace template for EKS (#518)

Co-authored-by: Noah Boyers <noah@MacBook-Pro.local>
Co-authored-by: Atif Ali <atif@coder.com>
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Noah Boyers <noah@coder.com>
This commit is contained in:
Noah
2025-12-31 06:42:19 -05:00
committed by GitHub
parent a3ba616aec
commit 3677e93e36
10 changed files with 1283 additions and 0 deletions
+1
View File
@@ -1,3 +1,4 @@
.DS_Store
# Logs
logs
*.log
+41
View File
@@ -0,0 +1,41 @@
# Local and OS files
.DS_Store
Thumbs.db
*.log
*.tmp
*.swp
*.bak
# Terraform
.terraform/
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup
crash.log
# Node / Bun / Python / other tool artifacts
node_modules/
bun.lockb
package-lock.json
__pycache__/
*.pyc
# Cloud credentials and keys
*.pem
*.key
*.p12
*.json
*.env
.envrc
aws-credentials
gcp.json
azure-creds.json
# Archives
*.zip
*.tar.gz
*.tgz
# Workspace artifacts
workspace/
output/
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+14
View File
@@ -0,0 +1,14 @@
---
display_name: "Noah Boyers"
bio: "Cloud & DevOps engineer with an MBA, building scalable multi-cloud infrastructure."
avatar: "./.images/avatar.png"
github: "noahboyers"
linkedin: "https://www.linkedin.com/in/nboyers"
website: "https://nobosoftware.com"
support_email: "hello@nobosoftware.com"
status: "community"
---
# Noah Boyers
Cloud and DevOps engineer focused on scalable, secure, and automated infrastructure across AWS, Azure, and GCP.
@@ -0,0 +1,80 @@
---
display_name: "Cloud DevOps Workspace"
description: "A multi-cloud DevOps workspace that runs on Amazon EKS and provides authenticated access to AWS, Azure, and GCP."
icon: "https://raw.githubusercontent.com/coder/coder-icons/main/icons/cloud-devops.svg"
tags:
- devops
- kubernetes
- aws
- eks
- multi-cloud
- terraform
- cdk
- pulumi
---
# Cloud DevOps Workspace
A secure, company-standard DevOps environment for platform and cloud engineers.
This template deploys workspaces **into an existing Amazon EKS cluster** and provides developers with tools and credentials to work with **AWS, Azure, and GCP** from inside their workspace.
Supports multiple Infrastructure-as-Code frameworks — **Terraform**, **AWS CDK**, and **Pulumi** — for flexible, multi-cloud development.
## Features
- **Multi-Cloud Ready** — authenticate to AWS, Azure, or GCP from a single workspace
- **Runs on EKS** — leverages existing Kubernetes infrastructure for scaling and security
- **IaC Tools Included** — Terraform, Terragrunt, CDK, Pulumi, tfsec, and more
- **Secure Isolation** — each workspace runs in its own Kubernetes namespace
- **Configurable Auth** — supports IRSA (AWS), Federated Identity (Azure), and WIF (GCP)
## Variables
| Variable | Description | Type | Default |
| ------------------------------------------------------------- | --------------------------------------------------------------- | ------ | ----------- |
| `host_cluster_name` | EKS cluster name where workspaces are deployed | string | — |
| `iac_tool` | Infrastructure-as-Code framework (`terraform`, `cdk`, `pulumi`) | string | `terraform` |
| `enable_aws` | Enable AWS authentication and tools | bool | `true` |
| `enable_azure` | Enable Azure authentication and tools | bool | `false` |
| `enable_gcp` | Enable GCP authentication and tools | bool | `false` |
| `aws_access_key_id` / `aws_secret_access_key` | AWS credentials (optional) | string | `""` |
| `azure_client_id` / `azure_client_secret` / `azure_tenant_id` | Azure credentials (optional) | string | `""` |
| `gcp_service_account` | GCP Service Account JSON (optional) | string | `""` |
## Runtime Architecture
| Layer | Platform | Purpose |
| ----------------------- | ------------------ | ------------------------------------------------------------ |
| **Infrastructure** | Amazon EKS | Where Coder deploys and runs the workspaces |
| **Workspace Container** | Ubuntu-based image | Developer environment (Terraform, CDK, Pulumi, CLIs) |
| **Cloud Access** | AWS / Azure / GCP | Target environments for deploying infrastructure or services |
## Required Permissions and Setup Steps
This template **runs on EKS** but allows developers inside the workspace to authenticate with **AWS, Azure, or GCP** using their own credentials or service identities.
### Coder & Infrastructure (Admin Setup)
Your Coder deployment must have:
- Network access to an **existing EKS cluster**
- The Coder Helm chart installed and healthy
- Terraform configured with access to the EKS API
#### Minimum AWS IAM Permissions
For the identity running the template (Coder service account, Terraform runner, or user):
```json
{
"Effect": "Allow",
"Action": [
"eks:DescribeCluster",
"eks:ListClusters",
"sts:GetCallerIdentity",
"sts:AssumeRole"
],
"Resource": "*"
}
```
@@ -0,0 +1,120 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "~> 0.23"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# --- Coder workspace context ---
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
# --- EKS connection ---
data "aws_eks_cluster" "eks" {
name = trimspace(var.host_cluster_name)
}
data "aws_eks_cluster_auth" "eks" {
name = trimspace(var.host_cluster_name)
}
provider "kubernetes" {
host = data.aws_eks_cluster.eks.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.eks.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.eks.token
}
# --- Namespace per workspace ---
resource "kubernetes_namespace" "workspace" {
metadata {
name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
labels = {
"coder.workspace" = data.coder_workspace.me.name
"coder.owner" = data.coder_workspace_owner.me.name
}
}
}
# --- ServiceAccount (IRSA optional) ---
resource "kubernetes_service_account" "workspace" {
metadata {
name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
namespace = kubernetes_namespace.workspace.metadata[0].name
annotations = var.enable_aws && var.aws_role_arn != "" ? {
"eks.amazonaws.com/role-arn" = var.aws_role_arn
} : {}
}
}
# --- Coder Agent definition ---
resource "coder_agent" "main" {
os = "linux"
arch = "amd64"
startup_script = file("${path.module}/scripts/setup-workspace.sh")
env = {
# IaC tool & cloud toggles
IAC_TOOL = var.iac_tool
ENABLE_AWS = tostring(var.enable_aws)
ENABLE_AZURE = tostring(var.enable_azure)
ENABLE_GCP = tostring(var.enable_gcp)
# Developer credentials
AWS_ACCESS_KEY_ID = var.aws_access_key_id
AWS_SECRET_ACCESS_KEY = var.aws_secret_access_key
AZURE_CLIENT_ID = var.azure_client_id
AZURE_TENANT_ID = var.azure_tenant_id
AZURE_CLIENT_SECRET = var.azure_client_secret
GCP_SERVICE_ACCOUNT = var.gcp_service_account
}
}
# --- Kubernetes Pod (runs workspace container) ---
resource "kubernetes_pod" "workspace" {
count = data.coder_workspace.me.start_count
metadata {
name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
namespace = kubernetes_namespace.workspace.metadata[0].name
labels = {
"app" = "coder-workspace"
"coder.owner" = data.coder_workspace_owner.me.name
"coder.agent" = "true"
}
}
spec {
service_account_name = kubernetes_service_account.workspace.metadata[0].name
container {
name = "workspace"
image = "codercom/enterprise-base:ubuntu"
command = ["/bin/bash", "-c", coder_agent.main.init_script]
env {
name = "CODER_AGENT_TOKEN"
value = coder_agent.main.token
}
resources {
requests = { cpu = "500m", memory = "1Gi" }
limits = { cpu = "2", memory = "4Gi" }
}
}
}
depends_on = [coder_agent.main]
}
@@ -0,0 +1,319 @@
#!/usr/bin/env bash
# cloud-auth.sh — Multi-cloud auth helpers (source this file, don't execute)
# Supports:
# - AWS: access keys or IRSA (via pod SA)
# - Azure: federated token or client secret
# - GCP: service account JSON or Workload Identity Federation (KSA -> SA)
set -euo pipefail
# -------- util --------
_has() { command -v "$1" > /dev/null 2>&1; }
_docker_ok() { _has docker && [[ -S /var/run/docker.sock ]]; }
cloud-auth-help() {
cat << 'EOHELP'
Multi-Cloud Authentication Helper — source this file:
source ~/workspace/cloud-auth.sh
Environment variables (read if set):
# Common toggles (optional)
ENABLE_AWS=true|false
ENABLE_AZURE=true|false
ENABLE_GCP=true|false
# AWS
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_SESSION_TOKEN=... # optional (STS); if unset, IRSA/IMDS is used
# Azure
AZURE_CLIENT_ID=...
AZURE_TENANT_ID=...
AZURE_CLIENT_SECRET=... # OR:
AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token
# GCP
GCP_PROJECT_ID=...
# Option A (Service Account JSON):
GCP_SERVICE_ACCOUNT='{ ... }'
# Option B (Workload Identity Federation):
GCP_WORKLOAD_IDENTITY_PROVIDER=projects/..../locations/global/workloadIdentityPools/.../providers/...
# (uses KSA token at /var/run/secrets/kubernetes.io/serviceaccount/token)
Functions:
# AWS
aws-login # ensures creds (keys or IRSA), sets region config if provided
aws-check # prints caller identity
aws-ecr-login # docker login to ECR (if docker socket present)
# Azure
azure-login # SP login via federated token OR client secret
azure-check # prints account info
azure-acr-login # docker login to ACR (requires AZURE_ACR_NAME)
# GCP
gcp-login # SA JSON or WIF
gcp-check # prints active gcloud account & project
gcp-gar-login # docker auth to GAR (requires GCP_REGION & PROJECT)
# Convenience
multicloud-login # calls the per-cloud logins if toggles are true
multicloud-check # calls the per-cloud checks
EOHELP
}
# -------- AWS --------
aws-login() {
[[ "${ENABLE_AWS:-true}" == "true" ]] || {
echo "AWS disabled"
return 0
}
if ! _has aws; then
echo "aws CLI not found"
return 1
fi
# If access keys are present, write standard files; otherwise rely on IRSA/IMDS
if [[ -n "${AWS_ACCESS_KEY_ID:-}" ]]; then
mkdir -p "${HOME}/.aws"
{
echo "[default]"
echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY:-}"
[[ -n "${AWS_SESSION_TOKEN:-}" ]] && echo "aws_session_token=${AWS_SESSION_TOKEN}"
} > "${HOME}/.aws/credentials"
if [[ -n "${AWS_REGION:-}" ]]; then
{
echo "[default]"
echo "region=${AWS_REGION}"
} > "${HOME}/.aws/config"
fi
fi
# Validate
if ! aws sts get-caller-identity > /dev/null 2>&1; then
echo "❌ AWS auth failed (neither valid keys nor IRSA available)"
return 1
fi
echo "✅ AWS auth OK"
}
aws-check() {
_has aws || {
echo "aws CLI not found"
return 1
}
aws sts get-caller-identity
}
aws-ecr-login() {
_has aws || {
echo "aws CLI not found"
return 1
}
_docker_ok || {
echo "️ docker socket not available; skipping ECR login"
return 0
}
: "${AWS_REGION:=us-east-1}"
aws-login > /dev/null || return 1
AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
aws ecr get-login-password --region "${AWS_REGION}" \
| docker login --username AWS --password-stdin \
"${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
export ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
echo "✅ ECR login OK → ${ECR_REGISTRY}"
}
# -------- Azure --------
azure-login() {
[[ "${ENABLE_AZURE:-false}" == "true" ]] || {
echo "Azure disabled"
return 0
}
_has az || {
echo "az CLI not found"
return 1
}
[[ -n "${AZURE_CLIENT_ID:-}" && -n "${AZURE_TENANT_ID:-}" ]] || {
echo "❌ Set AZURE_CLIENT_ID and AZURE_TENANT_ID"
return 1
}
if [[ -n "${AZURE_FEDERATED_TOKEN_FILE:-}" && -f "${AZURE_FEDERATED_TOKEN_FILE}" ]]; then
az login --service-principal \
--username "${AZURE_CLIENT_ID}" \
--tenant "${AZURE_TENANT_ID}" \
--federated-token "$(cat "${AZURE_FEDERATED_TOKEN_FILE}")" \
--allow-no-subscriptions
elif [[ -n "${AZURE_CLIENT_SECRET:-}" ]]; then
az login --service-principal \
-u "${AZURE_CLIENT_ID}" -p "${AZURE_CLIENT_SECRET}" \
--tenant "${AZURE_TENANT_ID}"
else
echo "❌ Provide AZURE_FEDERATED_TOKEN_FILE or AZURE_CLIENT_SECRET"
return 1
fi
echo "✅ Azure auth OK"
}
azure-check() {
_has az || {
echo "az CLI not found"
return 1
}
az account show
}
azure-acr-login() {
_has az || {
echo "az CLI not found"
return 1
}
_docker_ok || {
echo "️ docker socket not available; skipping ACR login"
return 0
}
[[ -n "${AZURE_ACR_NAME:-}" ]] || {
echo "❌ Set AZURE_ACR_NAME"
return 1
}
az account show > /dev/null 2>&1 || azure-login
az acr login --name "${AZURE_ACR_NAME}"
export ACR_REGISTRY="${AZURE_ACR_NAME}.azurecr.io"
echo "✅ ACR login OK → ${ACR_REGISTRY}"
}
# -------- GCP --------
gcp-login() {
[[ "${ENABLE_GCP:-false}" == "true" ]] || {
echo "GCP disabled"
return 0
}
_has gcloud || {
echo "gcloud not found"
return 1
}
if [[ -n "${GCP_SERVICE_ACCOUNT:-}" ]]; then
# Service Account JSON path
echo "${GCP_SERVICE_ACCOUNT}" > /tmp/gcp.json || {
echo "❌ Failed to write GCP credentials"
return 1
}
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json || {
echo "❌ Failed to set GCP credentials path"
return 1
}
gcloud auth activate-service-account --key-file=/tmp/gcp.json --quiet || {
echo "❌ GCP service account auth failed"
return 1
}
else
# Workload Identity Federation using KSA token + WIP provider
[[ -n "${GCP_WORKLOAD_IDENTITY_PROVIDER:-}" && -n "${GCP_PROJECT_ID:-}" ]] || {
echo "❌ Provide GCP_SERVICE_ACCOUNT JSON or set GCP_WORKLOAD_IDENTITY_PROVIDER & GCP_PROJECT_ID"
return 1
}
[[ -f "/var/run/secrets/kubernetes.io/serviceaccount/token" ]] || {
echo "❌ KSA token not found"
return 1
}
TMP="/tmp/gcp-wif-$$.json"
cat > "${TMP}" << 'EOF'
{
"type": "external_account",
"audience": "//iam.googleapis.com/${GCP_WORKLOAD_IDENTITY_PROVIDER}",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/var/run/secrets/kubernetes.io/serviceaccount/token",
"format": { "type": "text" }
}
}
EOF
[[ $? -eq 0 ]] || {
echo "❌ Failed to write GCP WIF config"
return 1
}
export GOOGLE_APPLICATION_CREDENTIALS="${TMP}" || {
echo "❌ Failed to set GCP credentials path"
return 1
}
gcloud auth login --cred-file="${GOOGLE_APPLICATION_CREDENTIALS}" --quiet || {
echo "❌ GCP WIF auth failed"
return 1
}
fi
if [[ -n "${GCP_PROJECT_ID:-}" ]]; then
gcloud config set project "${GCP_PROJECT_ID}" --quiet
fi
echo "✅ GCP auth OK"
}
gcp-check() {
_has gcloud || {
echo "gcloud not found"
return 1
}
gcloud auth list
gcloud config get-value project || true
}
gcp-gar-login() {
_docker_ok || {
echo "️ docker socket not available; skipping GAR login"
return 0
}
: "${GCP_REGION:=us-central1}"
[[ -n "${GCP_PROJECT_ID:-}" ]] || {
echo "❌ Set GCP_PROJECT_ID"
return 1
}
gcloud auth list --filter=status:ACTIVE --format="value(account)" > /dev/null || gcp-login
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet
export GAR_REGISTRY="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}"
echo "✅ GAR configured → ${GAR_REGISTRY}"
}
# -------- Convenience --------
multicloud-login() {
if [[ "${ENABLE_AWS:-true}" == "true" ]]; then
aws-login
fi
if [[ "${ENABLE_AZURE:-false}" == "true" ]]; then
azure-login
fi
if [[ "${ENABLE_GCP:-false}" == "true" ]]; then
gcp-login
fi
echo "✨ Multi-cloud login complete"
}
multicloud-check() {
if [[ "${ENABLE_AWS:-true}" == "true" ]]; then
echo "AWS:"
aws-check
echo
fi
if [[ "${ENABLE_AZURE:-false}" == "true" ]]; then
echo "Azure:"
azure-check
echo
fi
if [[ "${ENABLE_GCP:-false}" == "true" ]]; then
echo "GCP:"
gcp-check
echo
fi
}
echo "✨ cloud-auth loaded. Run 'cloud-auth-help' for usage."
@@ -0,0 +1,501 @@
#!/usr/bin/env bash
set -euo pipefail
# =========================
# Helpers & safe defaults
# =========================
log() { printf '%s %s\n' "👉" "$*"; }
ok() { printf '%s %s\n' "✅" "$*"; }
skip() { printf '%s %s\n' "⏭️" "$*"; }
warn() { printf '%s %s\n' "⚠️" "$*"; }
# Detect CPU arch (amd64/arm64)
arch() {
case "$(uname -m)" in
x86_64 | amd64) echo amd64 ;;
aarch64 | arm64) echo arm64 ;;
*) echo amd64 ;;
esac
}
# Map to Docker static tarball arch names
docker_tar_arch() {
case "$(arch)" in
amd64) echo x86_64 ;;
arm64) echo aarch64 ;;
*) echo x86_64 ;;
esac
}
SAFE_TMP="$(mktemp -d)"
trap 'rm -rf "$SAFE_TMP"' EXIT
safe_dl() { # url dest
curl -fL --retry 5 --retry-delay 2 --connect-timeout 10 -o "$2" "$1" || {
echo "Failed to download $1"
return 1
}
}
docker_ok() {
command -v docker > /dev/null 2>&1 && [[ -S /var/run/docker.sock ]]
}
# Ensure user bin dir
mkdir -p "$HOME/.local/bin" "$HOME/workspace/app"
export PATH="$HOME/.local/bin:$PATH"
# Inputs (with sane defaults)
IAC_TOOL="${IAC_TOOL:-terraform}"
TERRAFORM_VERSION="${TERRAFORM_VERSION:-1.6.0}"
ENABLE_AWS="${ENABLE_AWS:-true}"
ENABLE_AZURE="${ENABLE_AZURE:-false}"
ENABLE_GCP="${ENABLE_GCP:-false}"
AWS_REGION="${AWS_REGION:-}"
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-}"
AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}"
AZURE_CLIENT_ID="${AZURE_CLIENT_ID:-}"
AZURE_TENANT_ID="${AZURE_TENANT_ID:-}"
AZURE_CLIENT_SECRET="${AZURE_CLIENT_SECRET:-}"
AZURE_FEDERATED_TOKEN_FILE="${AZURE_FEDERATED_TOKEN_FILE:-}"
GCP_PROJECT_ID="${GCP_PROJECT_ID:-}"
GCP_SERVICE_ACCOUNT="${GCP_SERVICE_ACCOUNT:-}" # full JSON if not using WIF
REPO_URL="${REPO_URL:-${repo_url:-}}"
DEFAULT_BRANCH="${DEFAULT_BRANCH:-${default_branch:-main}}"
WORKDIR="${WORKDIR:-$HOME/workspace/app}"
GITHUB_TOKEN="${GITHUB_TOKEN:-${GIT_TOKEN:-}}"
GIT_AUTHOR_NAME="${GIT_AUTHOR_NAME:-}"
GIT_AUTHOR_EMAIL="${GIT_AUTHOR_EMAIL:-}"
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ Multi-Cloud DevOps Workspace Setup (no sudo) ║"
echo "╚════════════════════════════════════════════════════════════════╝"
echo
# ==========================================================
# Write multi-cloud helper functions to ~/workspace/cloud-auth.sh
# ==========================================================
cat > "${HOME}/workspace/cloud-auth.sh" << 'EOAUTHSCRIPT'
#!/usr/bin/env bash
set -euo pipefail
aws-ecr-login() {
: "${AWS_REGION:=us-east-1}"
if ! command -v aws >/dev/null 2>&1; then echo "aws CLI not found"; return 1; fi
if ! aws sts get-caller-identity &>/dev/null; then
echo "❌ AWS creds not available (IRSA or keys)"; return 1; fi
AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
if command -v docker >/dev/null 2>&1 && [[ -S /var/run/docker.sock ]]; then
aws ecr get-login-password --region "${AWS_REGION}" | \
docker login --username AWS --password-stdin \
"${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
export ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
echo "✅ ECR login OK → ${ECR_REGISTRY}"
else
echo "️ docker socket not available; skipping docker login"
fi
}
aws-check() { aws sts get-caller-identity && echo "✓ AWS creds valid"; }
azure-login() {
if ! command -v az >/dev/null 2>&1; then echo "az CLI not found"; return 1; fi
if [[ -n "${AZURE_FEDERATED_TOKEN_FILE:-}" && -f "${AZURE_FEDERATED_TOKEN_FILE}" ]]; then
az login --service-principal --username "${AZURE_CLIENT_ID}" \
--tenant "${AZURE_TENANT_ID}" \
--federated-token "$(cat "${AZURE_FEDERATED_TOKEN_FILE}")" \
--allow-no-subscriptions
elif [[ -n "${AZURE_CLIENT_SECRET:-}" ]]; then
az login --service-principal -u "${AZURE_CLIENT_ID}" -p "${AZURE_CLIENT_SECRET}" --tenant "${AZURE_TENANT_ID}"
else
echo "❌ Provide AZURE_FEDERATED_TOKEN_FILE or AZURE_CLIENT_SECRET"; return 1
fi
echo "✅ Azure auth OK"; az account show
}
azure-acr-login() {
[[ -n "${AZURE_ACR_NAME:-}" ]] || { echo "Set AZURE_ACR_NAME"; return 1; }
az account show &>/dev/null || azure-login
if command -v docker >/dev/null 2>&1 && [[ -S /var/run/docker.sock ]]; then
az acr login --name "${AZURE_ACR_NAME}"
export ACR_REGISTRY="${AZURE_ACR_NAME}.azurecr.io"
echo "✅ ACR login OK → ${ACR_REGISTRY}"
else
echo "️ docker socket not available; skipping docker login"
fi
}
azure-check() { az account show && echo "✓ Azure creds valid" || { echo "❌ Not logged in"; return 1; }; }
gcp-login() {
if ! command -v gcloud >/dev/null 2>&1; then echo "gcloud not found"; return 1; fi
if [[ -n "${GCP_SERVICE_ACCOUNT:-}" ]]; then
# SA JSON auth
echo "${GCP_SERVICE_ACCOUNT}" > /tmp/gcp.json || { echo "❌ Failed to write GCP credentials"; return 1; }
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json
gcloud auth activate-service-account --key-file=/tmp/gcp.json --quiet || { echo "❌ GCP auth failed"; return 1; }
else
echo "❌ Provide GCP_SERVICE_ACCOUNT JSON (WIF path not configured here)"; return 1
fi
[[ -n "${GCP_PROJECT_ID:-}" ]] && gcloud config set project "${GCP_PROJECT_ID}" --quiet || true
echo "✅ GCP auth OK"; gcloud auth list
}
gcp-gar-login() {
: "${GCP_REGION:=us-central1}"
[[ -n "${GCP_PROJECT_ID:-}" ]] || { echo "Set GCP_PROJECT_ID"; return 1; }
gcloud auth list --filter=status:ACTIVE --format="value(account)" &>/dev/null || gcp-login
if command -v docker >/dev/null 2>&1 && [[ -S /var/run/docker.sock ]]; then
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet
export GAR_REGISTRY="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}"
echo "✅ GAR configured → ${GAR_REGISTRY}"
else
echo "️ docker socket not available; skipping docker login"
fi
}
gcp-check() { gcloud auth list --filter=status:ACTIVE --format="value(account)" >/dev/null && echo "✓ GCP creds valid" || { echo "❌ Not logged in"; return 1; }; }
multicloud-login() {
[[ "${ENABLE_AWS:-false}" == "true" ]] && command -v aws >/dev/null && aws-ecr-login || true
[[ "${ENABLE_AZURE:-false}" == "true" ]] && command -v az >/dev/null && azure-login || true
[[ "${ENABLE_GCP:-false}" == "true" ]] && command -v gcloud >/dev/null && gcp-login || true
echo "✨ Multi-cloud login complete"
}
multicloud-check() {
[[ "${ENABLE_AWS:-false}" == "true" ]] && command -v aws >/dev/null && { echo "AWS:"; aws-check; echo; } || true
[[ "${ENABLE_AZURE:-false}" == "true" ]] && command -v az >/dev/null && { echo "Azure:"; azure-check; echo; } || true
[[ "${ENABLE_GCP:-false}" == "true" ]] && command -v gcloud >/dev/null && { echo "GCP:"; gcp-check; echo; } || true
}
cloud-auth-help() {
cat <<'EOHELP'
Multi-Cloud Authentication Helper
Functions:
AWS: aws-ecr-login, aws-check
Azure: azure-login, azure-acr-login, azure-check
GCP: gcp-login, gcp-gar-login, gcp-check
Multi: multicloud-login, multicloud-check, cloud-auth-help
EOHELP
return 0
}
echo "✨ Multi-cloud auth helpers loaded. Run 'cloud-auth-help' for help."
EOAUTHSCRIPT
chmod +x "${HOME}/workspace/cloud-auth.sh"
ok "Created ${HOME}/workspace/cloud-auth.sh"
echo
# =========================
# IaC tooling
# =========================
log "Installing IaC tooling (${IAC_TOOL})"
case "$IAC_TOOL" in
terraform)
if ! command -v terraform > /dev/null 2>&1; then
safe_dl "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_$(arch).zip" "$SAFE_TMP/tf.zip"
unzip -q "$SAFE_TMP/tf.zip" -d "$HOME/.local/bin"
ok "Terraform ${TERRAFORM_VERSION} installed"
else
ok "Terraform already installed ($(terraform version | head -1))"
fi
;;
cdk)
if ! command -v npm > /dev/null 2>&1; then
log "npm not found; installing Node via nvm"
export NVM_DIR="$HOME/.nvm"
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# shellcheck disable=SC1090
. "$NVM_DIR/nvm.sh"
nvm install --lts
nvm use --lts
# persist for future shells
grep -q 'NVM_DIR' "$HOME/.bashrc" 2> /dev/null || {
echo 'export NVM_DIR="$HOME/.nvm"' >> "$HOME/.bashrc"
echo '. "$NVM_DIR/nvm.sh"' >> "$HOME/.bashrc"
}
fi
npm install -g aws-cdk > /dev/null
ok "AWS CDK installed ($(cdk --version))"
;;
pulumi)
if ! command -v pulumi > /dev/null 2>&1; then
curl -fsSL https://get.pulumi.com | sh
export PATH="$PATH:$HOME/.pulumi/bin"
ok "Pulumi installed ($(pulumi version))"
else
ok "Pulumi already installed ($(pulumi version))"
fi
;;
*)
warn "Unknown IAC_TOOL=${IAC_TOOL}; skipping IaC install"
;;
esac
# Extras: Terragrunt, tflint, tfsec, terraform-docs, pre-commit
if ! command -v terragrunt > /dev/null 2>&1; then
TG_VER="0.54.0"
safe_dl "https://github.com/gruntwork-io/terragrunt/releases/download/v${TG_VER}/terragrunt_linux_$(arch)" "$HOME/.local/bin/terragrunt"
chmod +x "$HOME/.local/bin/terragrunt"
ok "Terragrunt v${TG_VER} installed"
fi
if ! command -v tflint > /dev/null 2>&1; then
# official installer handles arch
curl -fsSL https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
mv -f /tmp/tflint "$HOME/.local/bin/" 2> /dev/null || true
ok "tflint installed"
fi
if ! command -v tfsec > /dev/null 2>&1; then
TFSEC_VER="1.28.1"
safe_dl "https://github.com/aquasecurity/tfsec/releases/download/v${TFSEC_VER}/tfsec-linux-$(arch)" "$HOME/.local/bin/tfsec"
chmod +x "$HOME/.local/bin/tfsec"
ok "tfsec v${TFSEC_VER} installed"
fi
if ! command -v terraform-docs > /dev/null 2>&1; then
TFD_VER="0.17.0"
safe_dl "https://github.com/terraform-docs/terraform-docs/releases/download/v${TFD_VER}/terraform-docs-v${TFD_VER}-linux-$(arch).tar.gz" "$SAFE_TMP/terraform-docs.tgz"
tar -xzf "$SAFE_TMP/terraform-docs.tgz" -C "$SAFE_TMP"
install -m 0755 "$SAFE_TMP/terraform-docs" "$HOME/.local/bin/terraform-docs"
ok "terraform-docs v${TFD_VER} installed"
fi
if ! command -v pre-commit > /dev/null 2>&1; then
if command -v pip3 > /dev/null 2>&1; then
pip3 install --user --quiet pre-commit
ok "pre-commit installed"
elif command -v python3 > /dev/null 2>&1; then
python3 -m pip install --user --quiet pre-commit
ok "pre-commit installed"
else
warn "Python3/pip3 not found; skipping pre-commit"
fi
fi
# =========================
# Cloud CLIs (user-space)
# =========================
echo
log "Installing Cloud CLIs (user-space)"
# AWS CLI v2
if [[ "${ENABLE_AWS}" == "true" ]] && ! command -v aws > /dev/null 2>&1; then
safe_dl "https://awscli.amazonaws.com/awscli-exe-linux-$(arch).zip" "$SAFE_TMP/awscliv2.zip" \
|| safe_dl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" "$SAFE_TMP/awscliv2.zip"
unzip -q "$SAFE_TMP/awscliv2.zip" -d "$SAFE_TMP"
"$SAFE_TMP/aws/install" -i "$HOME/.local/aws-cli" -b "$HOME/.local/bin" > /dev/null
ok "AWS CLI installed"
fi
# Azure CLI
if [[ "${ENABLE_AZURE}" == "true" ]] && ! command -v az > /dev/null 2>&1; then
if command -v pip3 > /dev/null 2>&1; then
pip3 install --user --quiet azure-cli && ok "Azure CLI installed"
elif command -v python3 > /dev/null 2>&1; then
python3 -m pip install --user --quiet azure-cli && ok "Azure CLI installed"
else
warn "Python/pip not found; cannot install Azure CLI"
fi
fi
# Google Cloud SDK
if [[ "${ENABLE_GCP}" == "true" ]] && ! command -v gcloud > /dev/null 2>&1; then
GSDK_ARCH="$([[ "$(arch)" == amd64 ]] && echo x86_64 || echo arm)"
safe_dl "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-${GSDK_ARCH}.tar.gz" "$SAFE_TMP/gcloud.tgz"
tar -xzf "$SAFE_TMP/gcloud.tgz" -C "$HOME"
mv "$HOME/google-cloud-sdk" "$HOME/.local/google-cloud-sdk"
ln -sf "$HOME/.local/google-cloud-sdk/bin/"{gcloud,gsutil,bq} "$HOME/.local/bin/" || true
"$HOME/.local/google-cloud-sdk/install.sh" --quiet --rc-path /dev/null --path-update=false || true
ok "Google Cloud SDK installed"
fi
# =========================
# Container & K8s tools
# =========================
echo
log "Installing container & Kubernetes tools"
# Docker CLI (client only)
if ! command -v docker > /dev/null 2>&1; then
DOCKER_VER="25.0.5"
safe_dl "https://download.docker.com/linux/static/stable/$(docker_tar_arch)/docker-${DOCKER_VER}.tgz" "$SAFE_TMP/docker.tgz"
tar -xzf "$SAFE_TMP/docker.tgz" -C "$SAFE_TMP"
install -m 0755 "$SAFE_TMP/docker/docker" "$HOME/.local/bin/docker"
ok "Docker client installed"
fi
# kubectl
if ! command -v kubectl > /dev/null 2>&1; then
KREL="$(curl -fsSL https://dl.k8s.io/release/stable.txt)"
safe_dl "https://dl.k8s.io/release/${KREL}/bin/linux/$(arch)/kubectl" "$SAFE_TMP/kubectl"
install -m 0755 "$SAFE_TMP/kubectl" "$HOME/.local/bin/kubectl"
ok "kubectl ${KREL} installed"
fi
# Helm
if ! command -v helm > /dev/null 2>&1; then
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | USE_SUDO=false HELM_INSTALL_DIR="$HOME/.local/bin" bash
ok "Helm installed"
fi
# jq / yq
if ! command -v jq > /dev/null 2>&1; then
safe_dl "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-$(arch)" "$HOME/.local/bin/jq"
chmod +x "$HOME/.local/bin/jq"
ok "jq installed"
fi
if ! command -v yq > /dev/null 2>&1; then
safe_dl "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_$(arch)" "$HOME/.local/bin/yq"
chmod +x "$HOME/.local/bin/yq"
ok "yq installed"
fi
# =========================
# Cloud runtime auth (optional)
# =========================
echo
log "Configuring runtime cloud auth (if provided)"
# AWS keys (override IRSA if present)
if [[ "${ENABLE_AWS}" == "true" ]] && [[ -n "$AWS_ACCESS_KEY_ID" ]]; then
mkdir -p "$HOME/.aws"
{
echo "[default]"
echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY:-}"
[[ -n "$AWS_SESSION_TOKEN" ]] && echo "aws_session_token=${AWS_SESSION_TOKEN}"
} > "$HOME/.aws/credentials" || { warn "Failed to write AWS credentials"; }
if [[ -n "$AWS_REGION" ]]; then
{
echo "[default]"
echo "region=${AWS_REGION}"
} > "$HOME/.aws/config"
fi
ok "AWS runtime creds configured${AWS_REGION:+ (region ${AWS_REGION})}"
else
skip "AWS runtime creds not set"
fi
# Azure SP (client secret path; federated handled by helper)
if [[ "${ENABLE_AZURE}" == "true" ]] && [[ -n "$AZURE_CLIENT_ID" && -n "$AZURE_TENANT_ID" ]]; then
if command -v az > /dev/null 2>&1; then
if [[ -n "$AZURE_FEDERATED_TOKEN_FILE" && -f "$AZURE_FEDERATED_TOKEN_FILE" ]]; then
az login --service-principal --username "$AZURE_CLIENT_ID" \
--tenant "$AZURE_TENANT_ID" \
--federated-token "$(cat "$AZURE_FEDERATED_TOKEN_FILE")" \
--allow-no-subscriptions > /dev/null
ok "Azure federated login complete"
elif [[ -n "$AZURE_CLIENT_SECRET" ]]; then
az login --service-principal -u "$AZURE_CLIENT_ID" -p "$AZURE_CLIENT_SECRET" --tenant "$AZURE_TENANT_ID" > /dev/null
ok "Azure SP login complete"
else
skip "Azure creds not provided (need federated token file or client secret)"
fi
else
warn "Azure CLI not found; skipping login"
fi
else
skip "Azure runtime auth not configured"
fi
# GCP SA JSON
if [[ "${ENABLE_GCP}" == "true" ]] && [[ -n "$GCP_SERVICE_ACCOUNT" ]]; then
if command -v gcloud > /dev/null 2>&1; then
echo "$GCP_SERVICE_ACCOUNT" > /tmp/gcp.json || { warn "Failed to write GCP credentials"; }
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json
gcloud auth activate-service-account --key-file=/tmp/gcp.json > /dev/null || { warn "GCP auth failed"; }
[[ -n "$GCP_PROJECT_ID" ]] && gcloud config set project "$GCP_PROJECT_ID" --quiet || true
ok "GCP SA auth complete"
else
warn "gcloud not found; skipping GCP auth"
fi
else
skip "GCP runtime auth not configured"
fi
# =========================
# Git identity & bootstrap
# =========================
echo
log "Preparing workspace directory"
# Git identity
if [[ -n "$GIT_AUTHOR_NAME" ]]; then
git config --global user.name "$GIT_AUTHOR_NAME"
fi
if [[ -n "$GIT_AUTHOR_EMAIL" ]]; then
git config --global user.email "$GIT_AUTHOR_EMAIL"
fi
mkdir -p "$WORKDIR"
# Clone or init
if [[ -n "$REPO_URL" ]]; then
URL="$REPO_URL"
if [[ -n "$GITHUB_TOKEN" && "$URL" =~ ^https://github.com/ ]]; then
URL="${URL/https:\/\//https:\/\/${GITHUB_TOKEN}@}" || { warn "Failed to modify URL"; }
warn "Using GITHUB_TOKEN for private repo clone"
fi
if [[ ! -d "$WORKDIR/.git" ]]; then
log "Cloning ${REPO_URL} into ${WORKDIR}"
git clone "$URL" "$WORKDIR" || { warn "Failed to clone repository"; }
pushd "$WORKDIR" > /dev/null
git checkout "$DEFAULT_BRANCH" || git checkout -b "$DEFAULT_BRANCH"
popd > /dev/null
ok "Repository ready @ ${DEFAULT_BRANCH}"
else
ok "Repo already present at ${WORKDIR}"
fi
else
if [[ ! -d "$WORKDIR/.git" ]]; then
log "Initializing empty repository in ${WORKDIR}"
git init -q "$WORKDIR"
pushd "$WORKDIR" > /dev/null
git checkout -b "$DEFAULT_BRANCH" > /dev/null 2>&1 || true
popd > /dev/null
fi
ok "Workspace ready at ${WORKDIR}"
fi
# =========================
# Company Terraform skeleton
# =========================
echo
log "Creating company Terraform skeleton (optional)"
mkdir -p "$WORKDIR/terraform"/{environments/{dev,staging,prod},modules,policies,shared}
cat > "$WORKDIR/terraform/README.md" << 'EOREADME'
# Company Terraform Project
- `environments/` contains per-env stacks.
- `modules/` reusable infra modules.
- `policies/` sentinel/policy-as-code.
- `shared/` backend, providers, etc.
EOREADME
ok "Skeleton present at $WORKDIR/terraform"
# =========================
# PATH persistence tip
# =========================
if ! grep -q 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc" 2> /dev/null; then
echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> "$HOME/.bashrc"
fi
echo
ok "Workspace ready!"
echo " • IaC tool: ${IAC_TOOL}"
echo " • AWS enabled: ${ENABLE_AWS}"
echo " • Azure enabled: ${ENABLE_AZURE}"
echo " • GCP enabled: ${ENABLE_GCP}"
[[ -d "$WORKDIR/.git" ]] && echo " • Repo: ${REPO_URL:-<none>} @ ${DEFAULT_BRANCH}"
echo " • Auth helpers: source ~/workspace/cloud-auth.sh"
@@ -0,0 +1,87 @@
# Run 'terraform test' from this template directory (where main.tf lives)
# --- Mock cloud providers so no external calls happen ---
mock_provider "aws" {}
mock_provider "kubernetes" {}
# Provide fake values for data sources your template reads
override_data {
target = data.aws_eks_cluster.eks
values = {
name = "unit-test-eks"
endpoint = "https://example.eks.local"
certificate_authority = [{
data = base64encode("dummy-ca")
}]
}
}
override_data {
target = data.aws_eks_cluster_auth.eks
values = {
token = "dummy-token"
}
}
# ---------------------------
# 1) Validate configuration
# ---------------------------
run "validate" {
command = validate
}
# ---------------------------
# 2) Plan with representative inputs
# ---------------------------
run "plan_with_defaults" {
command = plan
variables {
host_cluster_name = "unit-test-eks"
# IaC/tooling toggles
iac_tool = "terraform"
enable_aws = true
enable_azure = false
enable_gcp = false
# Dev creds (empty OK for unit test)
aws_access_key_id = ""
aws_secret_access_key = ""
azure_client_id = ""
azure_tenant_id = ""
azure_client_secret = ""
gcp_service_account = ""
}
# Simple sanity assertions (adjust resource addresses to your template)
assert {
condition = can(resource.kubernetes_namespace.workspace)
error_message = "kubernetes_namespace.workspace was not created in plan."
}
assert {
condition = can(resource.coder_agent.main)
error_message = "coder_agent.main was not planned."
}
}
# ---------------------------
# 3) Plan with CDK selected
# ---------------------------
run "plan_with_cdk" {
command = plan
variables {
host_cluster_name = "unit-test-eks"
iac_tool = "cdk"
enable_aws = true
enable_azure = false
enable_gcp = false
}
# Ensure the env reflects choice (string map lookup)
assert {
condition = contains(keys(resource.coder_agent.main.env), "IAC_TOOL")
error_message = "IAC_TOOL env not present on coder_agent.main."
}
}
@@ -0,0 +1,120 @@
# --- Host cluster (where the workspace runs) ---
variable "host_cluster_name" {
description = "EKS cluster name"
type = string
validation {
condition = can(regex("^[0-9A-Za-z][0-9A-Za-z_-]*$", trimspace(var.host_cluster_name)))
error_message = "Cluster name must match ^[0-9A-Za-z][0-9A-Za-z_-]*$ (no leading space)."
}
}
# --- Admin: IaC tool & toggles ---
variable "iac_tool" {
description = "Infrastructure as Code tool"
type = string
default = "terraform"
validation {
condition = contains(["terraform", "cdk", "pulumi"], var.iac_tool)
error_message = "Must be one of: terraform, cdk, pulumi"
}
}
variable "enable_aws" {
type = bool
default = true
}
variable "enable_azure" {
type = bool
default = false
}
variable "enable_gcp" {
type = bool
default = false
}
# --- AWS ---
variable "aws_region" {
type = string
default = "us-west-2"
}
variable "aws_role_arn" {
type = string
default = "" # IRSA optional
}
variable "aws_access_key_id" {
type = string
default = ""
sensitive = true
}
variable "aws_secret_access_key" {
type = string
default = ""
sensitive = true
}
variable "aws_session_token" {
description = "Optional STS session token"
type = string
default = ""
sensitive = true
}
variable "repo_url" {
description = "Git repository to clone into the workspace (optional)"
type = string
default = ""
}
variable "default_branch" {
description = "Default branch name to use (if repo is empty or for initial checkout)"
type = string
default = "main"
}
# --- Azure ---
variable "azure_subscription_id" {
type = string
default = ""
}
variable "azure_tenant_id" {
type = string
default = ""
sensitive = true
}
variable "azure_client_id" {
type = string
default = ""
sensitive = true
}
variable "azure_client_secret" {
type = string
default = ""
sensitive = true
}
# --- GCP ---
variable "gcp_project_id" {
type = string
default = ""
}
variable "gcp_service_account" {
description = "Service Account JSON (paste full JSON) — leave empty if using WIF"
type = string
default = ""
sensitive = true
}