mirror of https://github.com/coder/coder.git
fix: properly apply metadata when multiple resources share the same id (#5443)
This commit is contained in:
parent
308a0602b6
commit
ac27cf8c07
|
@ -1069,12 +1069,12 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
|
|||
|
||||
// templateVersionResources returns the workspace agent resources associated
|
||||
// with a template version. A template can specify more than one resource to be
|
||||
// provisioned, each resource can have an agent that dials back to coderd.
|
||||
// The agents returned are informative of the template version, and do not
|
||||
// return agents associated with any particular workspace.
|
||||
// provisioned, each resource can have an agent that dials back to coderd. The
|
||||
// agents returned are informative of the template version, and do not return
|
||||
// agents associated with any particular workspace.
|
||||
func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var (
|
||||
ctx = r.Context()
|
||||
templateVersion = httpmw.TemplateVersionParam(r)
|
||||
template = httpmw.TemplateParam(r)
|
||||
)
|
||||
|
@ -1100,8 +1100,8 @@ func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request
|
|||
// and not any build logs for a workspace.
|
||||
// Eg: Logs returned from 'terraform plan' when uploading a new terraform file.
|
||||
func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var (
|
||||
ctx = r.Context()
|
||||
templateVersion = httpmw.TemplateVersionParam(r)
|
||||
template = httpmw.TemplateParam(r)
|
||||
)
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -270,7 +269,7 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
|
|||
return "", ctx.Err()
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
var out strings.Builder
|
||||
cmd := exec.CommandContext(killCtx, e.binaryPath, "graph") // #nosec
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = e.workdir
|
||||
|
@ -289,14 +288,13 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
|
|||
return out.String(), nil
|
||||
}
|
||||
|
||||
// revive:disable-next-line:flag-parameter
|
||||
func (e *executor) apply(
|
||||
ctx, killCtx context.Context, plan []byte, env []string, logr logSink,
|
||||
) (*proto.Provision_Response, error) {
|
||||
e.mut.Lock()
|
||||
defer e.mut.Unlock()
|
||||
|
||||
planFile, err := ioutil.TempFile("", "coder-terrafrom-plan")
|
||||
planFile, err := os.CreateTemp("", "coder-terrafrom-plan")
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create plan file: %w", err)
|
||||
}
|
||||
|
|
|
@ -68,8 +68,8 @@ type metadataItem struct {
|
|||
IsNull bool `mapstructure:"is_null"`
|
||||
}
|
||||
|
||||
// ConvertResources consumes Terraform state and a GraphViz representation produced by
|
||||
// `terraform graph` to produce resources consumable by Coder.
|
||||
// ConvertResources consumes Terraform state and a GraphViz representation
|
||||
// produced by `terraform graph` to produce resources consumable by Coder.
|
||||
// nolint:gocyclo
|
||||
func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Resource, error) {
|
||||
parsedGraph, err := gographviz.ParseString(rawGraph)
|
||||
|
@ -84,13 +84,9 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
resources := make([]*proto.Resource, 0)
|
||||
resourceAgents := map[string][]*proto.Agent{}
|
||||
|
||||
// Indexes Terraform resources by their label and ID.
|
||||
// The label is what "terraform graph" uses to reference nodes, and the ID
|
||||
// is used by "coder_metadata" resources to refer to their targets. (The ID
|
||||
// field is only available when reading a state file, and not when reading a
|
||||
// plan file.)
|
||||
// Indexes Terraform resources by their label.
|
||||
// The label is what "terraform graph" uses to reference nodes.
|
||||
tfResourceByLabel := map[string]*tfjson.StateResource{}
|
||||
resourceLabelByID := map[string]string{}
|
||||
var findTerraformResources func(mod *tfjson.StateModule)
|
||||
findTerraformResources = func(mod *tfjson.StateModule) {
|
||||
for _, module := range mod.ChildModules {
|
||||
|
@ -100,14 +96,6 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
label := convertAddressToLabel(resource.Address)
|
||||
// index by label
|
||||
tfResourceByLabel[label] = resource
|
||||
// index by ID, if it exists
|
||||
id, ok := resource.AttributeValues["id"]
|
||||
if ok {
|
||||
idString, ok := id.(string)
|
||||
if ok {
|
||||
resourceLabelByID[idString] = label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findTerraformResources(module)
|
||||
|
@ -319,58 +307,48 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
if resource.Type != "coder_metadata" {
|
||||
continue
|
||||
}
|
||||
|
||||
var attrs metadataAttributes
|
||||
err = mapstructure.Decode(resource.AttributeValues, &attrs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("decode metadata attributes: %w", err)
|
||||
}
|
||||
|
||||
var targetLabel string
|
||||
// This occurs in a plan, because there is no resource ID.
|
||||
// We attempt to find the closest node, just so we can hide it from the UI.
|
||||
if attrs.ResourceID == "" {
|
||||
resourceLabel := convertAddressToLabel(resource.Address)
|
||||
resourceLabel := convertAddressToLabel(resource.Address)
|
||||
|
||||
var attachedNode *gographviz.Node
|
||||
for _, node := range graph.Nodes.Lookup {
|
||||
// The node attributes surround the label with quotes.
|
||||
if strings.Trim(node.Attrs["label"], `"`) != resourceLabel {
|
||||
continue
|
||||
}
|
||||
attachedNode = node
|
||||
break
|
||||
}
|
||||
if attachedNode == nil {
|
||||
var attachedNode *gographviz.Node
|
||||
for _, node := range graph.Nodes.Lookup {
|
||||
// The node attributes surround the label with quotes.
|
||||
if strings.Trim(node.Attrs["label"], `"`) != resourceLabel {
|
||||
continue
|
||||
}
|
||||
var attachedResource *graphResource
|
||||
for _, resource := range findResourcesInGraph(graph, tfResourceByLabel, attachedNode.Name, 0, false) {
|
||||
if attachedResource == nil {
|
||||
// Default to the first resource because we have nothing to compare!
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
if resource.Depth < attachedResource.Depth {
|
||||
// There's a closer resource!
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label {
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
}
|
||||
if attachedResource == nil {
|
||||
continue
|
||||
}
|
||||
targetLabel = attachedResource.Label
|
||||
attachedNode = node
|
||||
break
|
||||
}
|
||||
if targetLabel == "" {
|
||||
targetLabel = resourceLabelByID[attrs.ResourceID]
|
||||
}
|
||||
if targetLabel == "" {
|
||||
if attachedNode == nil {
|
||||
continue
|
||||
}
|
||||
var attachedResource *graphResource
|
||||
for _, resource := range findResourcesInGraph(graph, tfResourceByLabel, attachedNode.Name, 0, false) {
|
||||
if attachedResource == nil {
|
||||
// Default to the first resource because we have nothing to compare!
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
if resource.Depth < attachedResource.Depth {
|
||||
// There's a closer resource!
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label {
|
||||
attachedResource = resource
|
||||
continue
|
||||
}
|
||||
}
|
||||
if attachedResource == nil {
|
||||
continue
|
||||
}
|
||||
targetLabel := attachedResource.Label
|
||||
|
||||
resourceHidden[targetLabel] = attrs.Hide
|
||||
resourceIcon[targetLabel] = attrs.Icon
|
||||
|
@ -416,9 +394,11 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
}
|
||||
|
||||
// convertAddressToLabel returns the Terraform address without the count
|
||||
// specifier. eg. "module.ec2_dev.ec2_instance.dev[0]" becomes "module.ec2_dev.ec2_instance.dev"
|
||||
// specifier.
|
||||
// eg. "module.ec2_dev.ec2_instance.dev[0]" becomes "module.ec2_dev.ec2_instance.dev"
|
||||
func convertAddressToLabel(address string) string {
|
||||
return strings.Split(address, "[")[0]
|
||||
cut, _, _ := strings.Cut(address, "[")
|
||||
return cut
|
||||
}
|
||||
|
||||
type graphResource struct {
|
||||
|
|
|
@ -8,14 +8,14 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
protobuf "github.com/golang/protobuf/proto"
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/provisioner/terraform"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
|
||||
protobuf "github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestConvertResources(t *testing.T) {
|
||||
|
@ -165,6 +165,53 @@ func TestConvertResources(t *testing.T) {
|
|||
Sensitive: true,
|
||||
}},
|
||||
}},
|
||||
// Tests that resources with the same id correctly get metadata applied
|
||||
// to them.
|
||||
"kubernetes-metadata": {{
|
||||
Name: "coder_workspace",
|
||||
Type: "kubernetes_service_account",
|
||||
}, {
|
||||
Name: "coder_workspace",
|
||||
Type: "kubernetes_config_map",
|
||||
}, {
|
||||
Name: "coder_workspace",
|
||||
Type: "kubernetes_role",
|
||||
}, {
|
||||
Name: "coder_workspace",
|
||||
Type: "kubernetes_role_binding",
|
||||
}, {
|
||||
Name: "coder_workspace",
|
||||
Type: "kubernetes_secret",
|
||||
}, {
|
||||
Name: "main",
|
||||
Type: "kubernetes_pod",
|
||||
Metadata: []*proto.Resource_Metadata{{
|
||||
Key: "cpu",
|
||||
Value: "1",
|
||||
}, {
|
||||
Key: "memory",
|
||||
Value: "1Gi",
|
||||
}, {
|
||||
Key: "gpu",
|
||||
Value: "1",
|
||||
}},
|
||||
Agents: []*proto.Agent{{
|
||||
Name: "main",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "amd64",
|
||||
StartupScript: " #!/bin/bash\n # home folder can be empty, so copying default bash settings\n if [ ! -f ~/.profile ]; then\n cp /etc/skel/.profile $HOME\n fi\n if [ ! -f ~/.bashrc ]; then\n cp /etc/skel/.bashrc $HOME\n fi\n # install and start code-server\n curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log\n code-server --auth none --port 13337 | tee code-server-install.log &\n",
|
||||
Apps: []*proto.App{
|
||||
{
|
||||
Icon: "/icon/code.svg",
|
||||
Slug: "code-server",
|
||||
DisplayName: "code-server",
|
||||
Url: "http://localhost:13337?folder=/home/coder",
|
||||
},
|
||||
},
|
||||
Auth: &proto.Agent_Token{},
|
||||
ConnectionTimeoutSeconds: 120,
|
||||
}},
|
||||
}},
|
||||
} {
|
||||
folderName := folderName
|
||||
expected := expected
|
||||
|
@ -210,6 +257,17 @@ func TestConvertResources(t *testing.T) {
|
|||
err = json.Unmarshal(data, &resourcesMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
slices.SortFunc(expectedNoMetadataMap, func(a, b map[string]interface{}) bool {
|
||||
//nolint:forcetypeassert
|
||||
return a["name"].(string)+a["type"].(string) <
|
||||
b["name"].(string)+b["type"].(string)
|
||||
})
|
||||
slices.SortFunc(resourcesMap, func(a, b map[string]interface{}) bool {
|
||||
//nolint:forcetypeassert
|
||||
return a["name"].(string)+a["type"].(string) <
|
||||
b["name"].(string)+b["type"].(string)
|
||||
})
|
||||
|
||||
require.Equal(t, expectedNoMetadataMap, resourcesMap)
|
||||
})
|
||||
|
||||
|
@ -251,6 +309,17 @@ func TestConvertResources(t *testing.T) {
|
|||
err = json.Unmarshal(data, &resourcesMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
slices.SortFunc(expectedMap, func(a, b map[string]interface{}) bool {
|
||||
//nolint:forcetypeassert
|
||||
return a["name"].(string)+a["type"].(string) <
|
||||
b["name"].(string)+b["type"].(string)
|
||||
})
|
||||
slices.SortFunc(resourcesMap, func(a, b map[string]interface{}) bool {
|
||||
//nolint:forcetypeassert
|
||||
return a["name"].(string)+a["type"].(string) <
|
||||
b["name"].(string)+b["type"].(string)
|
||||
})
|
||||
|
||||
require.Equal(t, expectedMap, resourcesMap)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.1",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"planned_values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.0",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
@ -17,11 +17,11 @@
|
|||
"connection_timeout": 120,
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "5c92d003-112d-4eb1-8e5f-d3009aa52fcb",
|
||||
"id": "411bdd93-0ea4-4376-a032-52b1fbf44ca5",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "fedbf404-c42d-4360-815b-5ffc34198df3",
|
||||
"token": "eeac85aa-19f9-4a50-8002-dfd11556081b",
|
||||
"troubleshooting_url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
@ -46,7 +46,7 @@
|
|||
"outputs": {
|
||||
"script": ""
|
||||
},
|
||||
"random": "5577006791947779410"
|
||||
"random": "5816533441722838433"
|
||||
},
|
||||
"sensitive_values": {
|
||||
"inputs": {},
|
||||
|
@ -61,7 +61,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8674665223082153551",
|
||||
"id": "5594550025354402054",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
2
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
generated
vendored
2
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
generated
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.1",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"planned_values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
|
10
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
10
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.0",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
@ -17,11 +17,11 @@
|
|||
"connection_timeout": 120,
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "6cc2be0d-fe90-4256-944f-482787433587",
|
||||
"id": "4dc52ff5-b270-47a2-8b6a-695b4872f07b",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "1927809c-5fcf-4fdd-94d7-9a619fb86d13",
|
||||
"token": "c5c8378e-66df-4f3f-94a2-84bff1dc6fc9",
|
||||
"troubleshooting_url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
@ -34,7 +34,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8674665223082153551",
|
||||
"id": "7372487656283423086",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -51,7 +51,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "5577006791947779410",
|
||||
"id": "2553224683756509362",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.1",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"planned_values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
|
10
provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json
generated
vendored
10
provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json
generated
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.0",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
@ -17,11 +17,11 @@
|
|||
"connection_timeout": 120,
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "bcaf2577-5dfd-4083-a446-789092a7babe",
|
||||
"id": "3cd9cbba-31f7-482c-a8a0-bf39dfe42dc2",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "862867af-cf08-4aea-a2af-70d0014f848b",
|
||||
"token": "8b063f22-9e66-4dbf-9f13-7b09ac2a470f",
|
||||
"troubleshooting_url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
@ -34,7 +34,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8674665223082153551",
|
||||
"id": "3370347998754925285",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -50,7 +50,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "5577006791947779410",
|
||||
"id": "4707694957868093590",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -6,6 +6,12 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||
for d in */; do
|
||||
pushd "$d"
|
||||
name=$(basename "$(pwd)")
|
||||
|
||||
# This needs care to update correctly.
|
||||
if [[ $name == "kubernetes-metadata" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
terraform init -upgrade
|
||||
terraform plan -out terraform.tfplan
|
||||
terraform show -json ./terraform.tfplan | jq >"$name".tfplan.json
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.1",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"planned_values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"format_version": "1.0",
|
||||
"terraform_version": "1.3.3",
|
||||
"terraform_version": "1.3.6",
|
||||
"values": {
|
||||
"root_module": {
|
||||
"resources": [
|
||||
|
@ -17,11 +17,11 @@
|
|||
"connection_timeout": 120,
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "30431432-7afb-4d73-8eeb-ee464a28e157",
|
||||
"id": "36189f12-6eed-4094-9179-6584a8659219",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "3ce9bbd8-0f31-4460-842b-8e9c1de9a567",
|
||||
"token": "907fa482-fd3b-44be-8cfb-4515e3122e78",
|
||||
"troubleshooting_url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
@ -34,8 +34,8 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "30431432-7afb-4d73-8eeb-ee464a28e157",
|
||||
"id": "679f9bf2-8887-4201-a5cd-e53913e8d361",
|
||||
"agent_id": "36189f12-6eed-4094-9179-6584a8659219",
|
||||
"id": "c9bd849e-ac37-440b-9c5b-a288344be41c",
|
||||
"instance_id": "example"
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -51,7 +51,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "5577006791947779410",
|
||||
"id": "4399071137990404376",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.6.5"
|
||||
}
|
||||
kubernetes = {
|
||||
source = "hashicorp/kubernetes"
|
||||
version = "~> 2.13.1"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "4.46.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "google_client_config" "provider" {}
|
||||
|
||||
data "google_container_cluster" "dev-4-2" {
|
||||
project = "coder-dev-1"
|
||||
name = "dev-4-2"
|
||||
location = "us-central1-a"
|
||||
}
|
||||
|
||||
locals {
|
||||
namespace = "colin-coder"
|
||||
workspace_name = lower("coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}")
|
||||
cpu = 1
|
||||
memory = "1Gi"
|
||||
gpu = 1
|
||||
}
|
||||
|
||||
provider "kubernetes" {
|
||||
host = "https://${data.google_container_cluster.dev-4-2.endpoint}"
|
||||
token = data.google_client_config.provider.access_token
|
||||
cluster_ca_certificate = base64decode(
|
||||
data.google_container_cluster.dev-4-2.master_auth[0].cluster_ca_certificate,
|
||||
)
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
os = "linux"
|
||||
arch = "amd64"
|
||||
startup_script = <<EOT
|
||||
#!/bin/bash
|
||||
# home folder can be empty, so copying default bash settings
|
||||
if [ ! -f ~/.profile ]; then
|
||||
cp /etc/skel/.profile $HOME
|
||||
fi
|
||||
if [ ! -f ~/.bashrc ]; then
|
||||
cp /etc/skel/.bashrc $HOME
|
||||
fi
|
||||
# install and start code-server
|
||||
curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log
|
||||
code-server --auth none --port 13337 | tee code-server-install.log &
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
}
|
||||
|
||||
resource "kubernetes_config_map" "coder_workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_service_account" "coder_workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
}
|
||||
resource "kubernetes_secret" "coder_workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
annotations = {
|
||||
"kubernetes.io/service-account.name" = local.workspace_name
|
||||
"kubernetes.io/service-account.namespace" = local.namespace
|
||||
}
|
||||
}
|
||||
type = "kubernetes.io/service-account-token"
|
||||
}
|
||||
|
||||
resource "kubernetes_role" "coder_workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
|
||||
rule {
|
||||
api_groups = ["*"]
|
||||
resources = ["configmaps"]
|
||||
resource_names = [local.workspace_name]
|
||||
verbs = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_role_binding" "coder_workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
role_ref {
|
||||
api_group = "rbac.authorization.k8s.io"
|
||||
kind = "Role"
|
||||
name = local.workspace_name
|
||||
}
|
||||
subject {
|
||||
kind = "ServiceAccount"
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_pod" "main" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
depends_on = [
|
||||
kubernetes_role.coder_workspace,
|
||||
kubernetes_role_binding.coder_workspace,
|
||||
kubernetes_service_account.coder_workspace,
|
||||
kubernetes_secret.coder_workspace,
|
||||
kubernetes_config_map.coder_workspace
|
||||
]
|
||||
metadata {
|
||||
name = local.workspace_name
|
||||
namespace = local.namespace
|
||||
}
|
||||
spec {
|
||||
service_account_name = local.workspace_name
|
||||
|
||||
container {
|
||||
name = "dev"
|
||||
image = "codercom/enterprise-base:ubuntu"
|
||||
command = ["sh", "-c", coder_agent.main.init_script]
|
||||
security_context {
|
||||
run_as_user = "1000"
|
||||
}
|
||||
env {
|
||||
name = "CODER_AGENT_TOKEN"
|
||||
value = coder_agent.main.token
|
||||
}
|
||||
resources {
|
||||
limits = {
|
||||
cpu = local.cpu
|
||||
memory = local.memory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "kubernetes_pod_main" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = kubernetes_pod.main[0].id
|
||||
item {
|
||||
key = "cpu"
|
||||
value = local.cpu
|
||||
}
|
||||
item {
|
||||
key = "memory"
|
||||
value = local.memory
|
||||
}
|
||||
item {
|
||||
key = "gpu"
|
||||
value = local.gpu
|
||||
}
|
||||
}
|
63
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfplan.dot
generated
vendored
Normal file
63
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfplan.dot
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
digraph {
|
||||
compound = "true"
|
||||
newrank = "true"
|
||||
subgraph "root" {
|
||||
"[root] coder_agent.main (expand)" [label = "coder_agent.main", shape = "box"]
|
||||
"[root] coder_app.code-server (expand)" [label = "coder_app.code-server", shape = "box"]
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" [label = "coder_metadata.kubernetes_pod_main", shape = "box"]
|
||||
"[root] data.coder_workspace.me (expand)" [label = "data.coder_workspace.me", shape = "box"]
|
||||
"[root] data.google_client_config.provider (expand)" [label = "data.google_client_config.provider", shape = "box"]
|
||||
"[root] data.google_container_cluster.dev-4-2 (expand)" [label = "data.google_container_cluster.dev-4-2", shape = "box"]
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" [label = "kubernetes_config_map.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_pod.main (expand)" [label = "kubernetes_pod.main", shape = "box"]
|
||||
"[root] kubernetes_role.coder_workspace (expand)" [label = "kubernetes_role.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" [label = "kubernetes_role_binding.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" [label = "kubernetes_secret.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" [label = "kubernetes_service_account.coder_workspace", shape = "box"]
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"]" [label = "provider[\"registry.terraform.io/coder/coder\"]", shape = "diamond"]
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"]" [label = "provider[\"registry.terraform.io/hashicorp/google\"]", shape = "diamond"]
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" [label = "provider[\"registry.terraform.io/hashicorp/kubernetes\"]", shape = "diamond"]
|
||||
"[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
|
||||
"[root] coder_app.code-server (expand)" -> "[root] coder_agent.main (expand)"
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" -> "[root] kubernetes_pod.main (expand)"
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" -> "[root] local.gpu (expand)"
|
||||
"[root] data.coder_workspace.me (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
|
||||
"[root] data.google_client_config.provider (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"]"
|
||||
"[root] data.google_container_cluster.dev-4-2 (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"]"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] coder_agent.main (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_config_map.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_role.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_role_binding.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_secret.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_service_account.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] local.cpu (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] local.memory (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] local.workspace_name (expand)" -> "[root] data.coder_workspace.me (expand)"
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_app.code-server (expand)"
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.kubernetes_pod_main (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)" -> "[root] data.google_client_config.provider (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)" -> "[root] data.google_container_cluster.dev-4-2 (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"] (close)" -> "[root] kubernetes_pod.main (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" -> "[root] data.google_client_config.provider (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" -> "[root] data.google_container_cluster.dev-4-2 (expand)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"] (close)"
|
||||
}
|
||||
}
|
||||
|
2179
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfplan.json
generated
vendored
Normal file
2179
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfplan.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
63
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfstate.dot
generated
vendored
Normal file
63
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfstate.dot
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
digraph {
|
||||
compound = "true"
|
||||
newrank = "true"
|
||||
subgraph "root" {
|
||||
"[root] coder_agent.main (expand)" [label = "coder_agent.main", shape = "box"]
|
||||
"[root] coder_app.code-server (expand)" [label = "coder_app.code-server", shape = "box"]
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" [label = "coder_metadata.kubernetes_pod_main", shape = "box"]
|
||||
"[root] data.coder_workspace.me (expand)" [label = "data.coder_workspace.me", shape = "box"]
|
||||
"[root] data.google_client_config.provider (expand)" [label = "data.google_client_config.provider", shape = "box"]
|
||||
"[root] data.google_container_cluster.dev-4-2 (expand)" [label = "data.google_container_cluster.dev-4-2", shape = "box"]
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" [label = "kubernetes_config_map.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_pod.main (expand)" [label = "kubernetes_pod.main", shape = "box"]
|
||||
"[root] kubernetes_role.coder_workspace (expand)" [label = "kubernetes_role.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" [label = "kubernetes_role_binding.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" [label = "kubernetes_secret.coder_workspace", shape = "box"]
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" [label = "kubernetes_service_account.coder_workspace", shape = "box"]
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"]" [label = "provider[\"registry.terraform.io/coder/coder\"]", shape = "diamond"]
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"]" [label = "provider[\"registry.terraform.io/hashicorp/google\"]", shape = "diamond"]
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" [label = "provider[\"registry.terraform.io/hashicorp/kubernetes\"]", shape = "diamond"]
|
||||
"[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
|
||||
"[root] coder_app.code-server (expand)" -> "[root] coder_agent.main (expand)"
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" -> "[root] kubernetes_pod.main (expand)"
|
||||
"[root] coder_metadata.kubernetes_pod_main (expand)" -> "[root] local.gpu (expand)"
|
||||
"[root] data.coder_workspace.me (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
|
||||
"[root] data.google_client_config.provider (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"]"
|
||||
"[root] data.google_container_cluster.dev-4-2 (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"]"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_config_map.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] coder_agent.main (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_config_map.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_role.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_role_binding.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_secret.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] kubernetes_service_account.coder_workspace (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] local.cpu (expand)"
|
||||
"[root] kubernetes_pod.main (expand)" -> "[root] local.memory (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_role.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_role_binding.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_secret.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] local.namespace (expand)"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] local.workspace_name (expand)"
|
||||
"[root] kubernetes_service_account.coder_workspace (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]"
|
||||
"[root] local.workspace_name (expand)" -> "[root] data.coder_workspace.me (expand)"
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_app.code-server (expand)"
|
||||
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.kubernetes_pod_main (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)" -> "[root] data.google_client_config.provider (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)" -> "[root] data.google_container_cluster.dev-4-2 (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"] (close)" -> "[root] kubernetes_pod.main (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" -> "[root] data.google_client_config.provider (expand)"
|
||||
"[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"]" -> "[root] data.google_container_cluster.dev-4-2 (expand)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/google\"] (close)"
|
||||
"[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/kubernetes\"] (close)"
|
||||
}
|
||||
}
|
||||
|
1116
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfstate.json
generated
vendored
Normal file
1116
provisioner/terraform/testdata/kubernetes-metadata/kubernetes-metadata.tfstate.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue