From 313ec59d46f5587b2d281128814a985583f85d9d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 23 Feb 2024 23:49:47 +0500 Subject: [PATCH] Add terraform validation to linting (#170) Co-authored-by: Mathias Fredriksson --- .sample/main.tf | 2 +- jetbrains-gateway/main.test.ts | 8 +-- jetbrains-gateway/main.tf | 124 +++++++++++++++++++++------------ package.json | 2 +- terraform_validate.sh | 29 ++++++++ 5 files changed, 114 insertions(+), 51 deletions(-) create mode 100755 terraform_validate.sh diff --git a/.sample/main.tf b/.sample/main.tf index 733f121..9cbb464 100644 --- a/.sample/main.tf +++ b/.sample/main.tf @@ -69,7 +69,7 @@ resource "coder_app" "MODULE_NAME" { slug = "MODULE_NAME" display_name = "MODULE_NAME" url = "http://localhost:${var.port}" - icon = loocal.icon_url + icon = local.icon_url subdomain = false share = "owner" diff --git a/jetbrains-gateway/main.test.ts b/jetbrains-gateway/main.test.ts index bc0ef2f..b327e41 100644 --- a/jetbrains-gateway/main.test.ts +++ b/jetbrains-gateway/main.test.ts @@ -11,18 +11,16 @@ describe("jetbrains-gateway", async () => { await testRequiredVariables(import.meta.dir, { agent_id: "foo", agent_name: "foo", - folder: "/baz/", + folder: "/home/foo", }); it("default to first ide", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", agent_name: "foo", - folder: "/baz/", + folder: "/home/foo", jetbrains_ides: '["IU", "GO", "PY"]', }); - expect(state.outputs.jetbrains_ides.value).toBe( - '["IU","232.10203.10","https://download.jetbrains.com/idea/ideaIU-2023.2.4.tar.gz"]', - ); + expect(state.outputs.identifier.value).toBe("IU"); }); }); diff --git a/jetbrains-gateway/main.tf b/jetbrains-gateway/main.tf index af2a0ea..b7bd5c3 100644 --- a/jetbrains-gateway/main.tf +++ b/jetbrains-gateway/main.tf @@ -22,6 +22,10 @@ variable "agent_name" { variable "folder" { type = string description = "The directory to open in the IDE. e.g. /home/coder/project" + validation { + condition = can(regex("^(?:/[^/]+)+$", var.folder)) + error_message = "The folder must be a full path and must not start with a ~." + } } variable "default" { @@ -30,10 +34,6 @@ variable "default" { description = "Default IDE" } -locals { - supported_ides = ["IU", "PS", "WS", "PY", "CL", "GO", "RM"] -} - variable "jetbrains_ide_versions" { type = map(object({ build_number = string @@ -69,29 +69,28 @@ variable "jetbrains_ide_versions" { build_number = "232.10203.15" version = "2023.2.4" } - } validation { condition = ( alltrue([ - for code in var.jetbrains_ide_versions : contains(local.supported_ides, code) + for code in keys(var.jetbrains_ide_versions) : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM"], code) ]) ) - error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", local.supported_ides)}." + error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM"])}." } } variable "jetbrains_ides" { type = list(string) description = "The list of IDE product codes." - default = local.supported_ides + default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM"] validation { condition = ( alltrue([ - for code in var.jetbrains_ides : contains(local.supported_ides, code) + for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM"], code) ]) ) - error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", local.supported_ides)}." + error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM"])}." } # check if the list is empty validation { @@ -108,58 +107,71 @@ variable "jetbrains_ides" { locals { jetbrains_ides = { "GO" = { - icon = "/icon/goland.svg", - name = "GoLand", - value = jsonencode(["GO", var.jetbrains_ide_versions["GO"].build_number, "https://download.jetbrains.com/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"]) + icon = "/icon/goland.svg", + name = "GoLand", + identifier = "GO", + build_number = var.jetbrains_ide_versions["GO"].build_number, + download_link = "https://download.jetbrains.com/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz" }, "WS" = { - icon = "/icon/webstorm.svg", - name = "WebStorm", - value = jsonencode(["WS", var.jetbrains_ide_versions["WS"].build_number, "https://download.jetbrains.com/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"]) + icon = "/icon/webstorm.svg", + name = "WebStorm", + identifier = "WS", + build_number = var.jetbrains_ide_versions["WS"].build_number, + download_link = "https://download.jetbrains.com/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz" }, "IU" = { - icon = "/icon/intellij.svg", - name = "IntelliJ IDEA Ultimate", - value = jsonencode(["IU", var.jetbrains_ide_versions["IU"].build_number, "https://download.jetbrains.com/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"]) + icon = "/icon/intellij.svg", + name = "IntelliJ IDEA Ultimate", + identifier = "IU", + build_number = var.jetbrains_ide_versions["IU"].build_number, + download_link = "https://download.jetbrains.com/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz" }, "PY" = { - icon = "/icon/pycharm.svg", - name = "PyCharm Professional", - value = jsonencode(["PY", var.jetbrains_ide_versions["PY"].build_number, "https://download.jetbrains.com/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz"]) + icon = "/icon/pycharm.svg", + name = "PyCharm Professional", + identifier = "PY", + build_number = var.jetbrains_ide_versions["PY"].build_number, + download_link = "https://download.jetbrains.com/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz" }, "CL" = { - icon = "/icon/clion.svg", - name = "CLion", - value = jsonencode(["CL", var.jetbrains_ide_versions["CL"].build_number, "https://download.jetbrains.com/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"]) + icon = "/icon/clion.svg", + name = "CLion", + identifier = "CL", + build_number = var.jetbrains_ide_versions["CL"].build_number, + download_link = "https://download.jetbrains.com/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz" }, "PS" = { - icon = "/icon/phpstorm.svg", - name = "PhpStorm", - value = jsonencode(["PS", var.jetbrains_ide_versions["PS"].build_number, "https://download.jetbrains.com/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"]) + icon = "/icon/phpstorm.svg", + name = "PhpStorm", + identifier = "PS", + build_number = var.jetbrains_ide_versions["PS"].build_number, + download_link = "https://download.jetbrains.com/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz" }, "RM" = { - icon = "/icon/rubymine.svg", - name = "RubyMine", - value = jsonencode(["RM", var.jetbrains_ide_versions["RM"].build_number, "https://download.jetbrains.com/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz"]) + icon = "/icon/rubymine.svg", + name = "RubyMine", + identifier = "RM", + build_number = var.jetbrains_ide_versions["RM"].build_number, + download_link = "https://download.jetbrains.com/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz" } } } data "coder_parameter" "jetbrains_ide" { - type = "list(string)" + type = "string" name = "jetbrains_ide" display_name = "JetBrains IDE" icon = "/icon/gateway.svg" mutable = true - # check if default is in the jet_brains_ides list and if it is not empty or null otherwise set it to null - default = var.default != null && var.default != "" && contains(var.jetbrains_ides, var.default) ? local.jetbrains_ides[var.default].value : local.jetbrains_ides[var.jetbrains_ides[0]].value + default = var.default == "" ? var.jetbrains_ides[0] : var.default dynamic "option" { - for_each = { for key, value in local.jetbrains_ides : key => value if contains(var.jetbrains_ides, key) } + for_each = var.jetbrains_ides content { - icon = option.value.icon - name = option.value.name - value = option.value.value + icon = lookup(local.jetbrains_ides, option.value).icon + name = lookup(local.jetbrains_ides, option.value).name + value = lookup(local.jetbrains_ides, option.value).identifier } } } @@ -168,9 +180,9 @@ data "coder_workspace" "me" {} resource "coder_app" "gateway" { agent_id = var.agent_id - display_name = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].name slug = "gateway" - icon = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].icon + display_name = try(lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).name, "JetBrains IDE") + icon = try(lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).icon, "/icon/gateway.svg") external = true url = join("", [ "jetbrains-gateway://connect#type=coder&workspace=", @@ -184,14 +196,38 @@ resource "coder_app" "gateway" { "&token=", "$SESSION_TOKEN", "&ide_product_code=", - jsondecode(data.coder_parameter.jetbrains_ide.value)[0], + local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].identifier, "&ide_build_number=", - jsondecode(data.coder_parameter.jetbrains_ide.value)[1], + local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number, "&ide_download_link=", - jsondecode(data.coder_parameter.jetbrains_ide.value)[2], + local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link ]) } -output "jetbrains_ides" { +output "identifier" { value = data.coder_parameter.jetbrains_ide.value } + +output "name" { + value = coder_app.gateway.display_name +} + +output "icon" { + value = coder_app.gateway.icon +} + +output "download_link" { + value = lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).download_link +} + +output "build_number" { + value = lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).build_number +} + +output "version" { + value = var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version +} + +output "url" { + value = coder_app.gateway.url +} \ No newline at end of file diff --git a/package.json b/package.json index bab18d3..2570de4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "test": "bun test", "fmt": "bun x prettier -w **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt **/*.tf .sample/main.tf", "fmt:ci": "bun x prettier --check **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt -check **/*.tf .sample/main.tf", - "lint": "bun run lint.ts", + "lint": "bun run lint.ts && ./terraform_validate.sh", "update-version": "./update-version.sh" }, "devDependencies": { diff --git a/terraform_validate.sh b/terraform_validate.sh new file mode 100755 index 0000000..292c94c --- /dev/null +++ b/terraform_validate.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -euo pipefail + +# Function to run terraform init and validate in a directory +run_terraform() { + local dir="$1" + echo "Running terraform init and validate in $dir" + pushd "$dir" + terraform init -upgrade + terraform validate + popd +} + +# Main script +main() { + # Get the directory of the script + script_dir=$(dirname "$(readlink -f "$0")") + + # Get all subdirectories in the repository + subdirs=$(find "$script_dir" -mindepth 1 -maxdepth 1 -type d -not -name ".*" | sort) + + for dir in $subdirs; do + run_terraform "$dir" + done +} + +# Run the main script +main