Skip to content

Commit 3e8547b

Browse files
feat: add coderd_template resource (#35)
1 parent 75f030f commit 3e8547b

File tree

14 files changed

+1406
-4
lines changed

14 files changed

+1406
-4
lines changed

docs/resources/template.md

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coderd_template Resource - coderd"
4+
subcategory: ""
5+
description: |-
6+
A Coder template
7+
---
8+
9+
# coderd_template (Resource)
10+
11+
A Coder template
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `acl` (Attributes) Access control list for the template. (see [below for nested schema](#nestedatt--acl))
21+
- `name` (String) The name of the template.
22+
- `versions` (Attributes List) (see [below for nested schema](#nestedatt--versions))
23+
24+
### Optional
25+
26+
- `allow_user_auto_start` (Boolean)
27+
- `allow_user_auto_stop` (Boolean)
28+
- `description` (String) A description of the template.
29+
- `display_name` (String) The display name of the template. Defaults to the template name.
30+
- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
31+
- `organization_id` (String) The ID of the organization. Defaults to the provider's default organization
32+
33+
### Read-Only
34+
35+
- `id` (String) The ID of the template.
36+
37+
<a id="nestedatt--acl"></a>
38+
### Nested Schema for `acl`
39+
40+
Required:
41+
42+
- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
43+
- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))
44+
45+
<a id="nestedatt--acl--groups"></a>
46+
### Nested Schema for `acl.groups`
47+
48+
Required:
49+
50+
- `id` (String)
51+
- `role` (String)
52+
53+
54+
<a id="nestedatt--acl--users"></a>
55+
### Nested Schema for `acl.users`
56+
57+
Required:
58+
59+
- `id` (String)
60+
- `role` (String)
61+
62+
63+
64+
<a id="nestedatt--versions"></a>
65+
### Nested Schema for `versions`
66+
67+
Required:
68+
69+
- `directory` (String) A path to the directory to create the template version from. Changes in the directory contents will trigger the creation of a new template version.
70+
71+
Optional:
72+
73+
- `active` (Boolean) Whether this version is the active version of the template. Only one version can be active at a time.
74+
- `message` (String) A message describing the changes in this version of the template. Messages longer than 72 characters will be truncated.
75+
- `name` (String) The name of the template version. Automatically generated if not provided.
76+
- `provisioner_tags` (Attributes Set) Provisioner tags for the template version. (see [below for nested schema](#nestedatt--versions--provisioner_tags))
77+
- `tf_vars` (Attributes Set) Terraform variables for the template version. (see [below for nested schema](#nestedatt--versions--tf_vars))
78+
79+
Read-Only:
80+
81+
- `directory_hash` (String)
82+
- `id` (String)
83+
84+
<a id="nestedatt--versions--provisioner_tags"></a>
85+
### Nested Schema for `versions.provisioner_tags`
86+
87+
Required:
88+
89+
- `name` (String)
90+
- `value` (String)
91+
92+
93+
<a id="nestedatt--versions--tf_vars"></a>
94+
### Nested Schema for `versions.tf_vars`
95+
96+
Required:
97+
98+
- `name` (String)
99+
- `value` (String)

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ require (
120120
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
121121
github.com/shopspring/decimal v1.3.1 // indirect
122122
github.com/spaolacci/murmur3 v1.1.0 // indirect
123+
github.com/spf13/afero v1.11.0 // indirect
123124
github.com/spf13/cast v1.6.0 // indirect
124125
github.com/spf13/pflag v1.0.5 // indirect
125126
github.com/tinylib/msgp v1.1.8 // indirect

go.sum

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 h1:KHblWIE/KHOwQ6lEbMZt6YpcGve2FEZ1sDtrW1Am5UI=
22
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ=
3-
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
3+
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
44
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
55
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
66
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
@@ -81,8 +81,6 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo
8181
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
8282
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
8383
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
84-
github.com/coder/coder/v2 v2.13.0 h1:MlkRGqQcCAdwIkLc9iV8sQfT4jB3EThHopG0jF3BuFE=
85-
github.com/coder/coder/v2 v2.13.0/go.mod h1:Gxc79InMB6b+sncuDUORtFLWi7aKshvis3QrMUhpq5Q=
8684
github.com/coder/coder/v2 v2.13.1 h1:tCd8ljqIAufbVcBr8ODS1QbsrjJbmOIvgDkvdd/JMXc=
8785
github.com/coder/coder/v2 v2.13.1/go.mod h1:Gxc79InMB6b+sncuDUORtFLWi7aKshvis3QrMUhpq5Q=
8886
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
@@ -134,6 +132,8 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
134132
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
135133
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
136134
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
135+
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
136+
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
137137
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
138138
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
139139
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
@@ -390,6 +390,8 @@ github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L
390390
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
391391
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
392392
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
393+
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
394+
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
393395
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
394396
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
395397
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=

integration/integration_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,45 @@ func TestIntegration(t *testing.T) {
105105
assert.Equal(t, group.QuotaAllowance, 100)
106106
},
107107
},
108+
{
109+
name: "template-test",
110+
preF: func(t testing.TB, c *codersdk.Client) {},
111+
assertF: func(t testing.TB, c *codersdk.Client) {
112+
defaultOrg, err := c.OrganizationByName(ctx, "first-organization")
113+
assert.NoError(t, err)
114+
user, err := c.User(ctx, "ethan")
115+
require.NoError(t, err)
116+
117+
// Check template metadata
118+
templates, err := c.Templates(ctx)
119+
require.NoError(t, err)
120+
require.Len(t, templates, 1)
121+
require.Equal(t, "example-template", templates[0].Name)
122+
require.False(t, templates[0].AllowUserAutostart)
123+
require.False(t, templates[0].AllowUserAutostop)
124+
125+
// Check versions
126+
versions, err := c.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
127+
TemplateID: templates[0].ID,
128+
})
129+
require.NoError(t, err)
130+
require.Len(t, versions, 2)
131+
require.Equal(t, "latest", versions[0].Name)
132+
require.NotEmpty(t, versions[0].ID)
133+
require.Equal(t, templates[0].ID, *versions[0].TemplateID)
134+
require.Equal(t, templates[0].ActiveVersionID, versions[0].ID)
135+
136+
// Check ACL
137+
acl, err := c.TemplateACL(ctx, templates[0].ID)
138+
require.NoError(t, err)
139+
require.Len(t, acl.Groups, 1)
140+
require.Equal(t, codersdk.TemplateRoleUse, acl.Groups[0].Role)
141+
require.Equal(t, defaultOrg.ID, acl.Groups[0].ID)
142+
require.Len(t, acl.Users, 1)
143+
require.Equal(t, codersdk.TemplateRoleAdmin, acl.Users[0].Role)
144+
require.Equal(t, user.ID, acl.Users[0].ID)
145+
},
146+
},
108147
} {
109148
t.Run(tt.name, func(t *testing.T) {
110149
client := StartCoder(ctx, t, tt.name, true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
variable "name" {
2+
type = string
3+
}
4+
5+
resource "local_file" "a" {
6+
filename = "${path.module}/a.txt"
7+
content = "hello ${var.name}"
8+
}
9+
10+
output "a" {
11+
value = local_file.a.content
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
variable "name" {
2+
type = string
3+
}
4+
5+
resource "local_file" "a" {
6+
filename = "${path.module}/a.txt"
7+
content = "hello ${var.name}"
8+
}
9+
10+
output "a" {
11+
value = local_file.a.content
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
name = "world"

integration/template-test/main.tf

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
terraform {
2+
required_providers {
3+
coderd = {
4+
source = "coder/coderd"
5+
version = ">=0.0.0"
6+
}
7+
}
8+
}
9+
10+
resource "coderd_user" "ethan" {
11+
username = "ethan"
12+
name = "Ethan Coolguy"
13+
email = "test@coder.com"
14+
roles = ["owner", "template-admin"]
15+
login_type = "password"
16+
password = "SomeSecurePassword!"
17+
suspended = false
18+
}
19+
20+
21+
data "coderd_organization" "default" {
22+
is_default = true
23+
}
24+
25+
resource "coderd_template" "sample" {
26+
name = "example-template"
27+
allow_user_auto_stop = false
28+
allow_user_auto_start = false
29+
acl = {
30+
groups = [
31+
{
32+
id = data.coderd_organization.default.id
33+
role = "use"
34+
}
35+
]
36+
users = [
37+
{
38+
id = resource.coderd_user.ethan.id
39+
role = "admin"
40+
}
41+
]
42+
}
43+
versions = [
44+
{
45+
name = "latest"
46+
directory = "./example-template"
47+
active = true
48+
tf_vars = [
49+
{
50+
name = "name"
51+
value = "world"
52+
},
53+
]
54+
},
55+
{
56+
name = "legacy"
57+
directory = "./example-template-2"
58+
active = false
59+
tf_vars = [
60+
{
61+
name = "name"
62+
value = "ethan"
63+
},
64+
]
65+
}
66+
]
67+
}

internal/provider/logger.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
6+
"cdr.dev/slog"
7+
"github.com/hashicorp/terraform-plugin-log/tflog"
8+
)
9+
10+
var _ slog.Sink = &tfLogSink{}
11+
12+
type tfLogSink struct {
13+
tfCtx context.Context
14+
}
15+
16+
func newTFLogSink(tfCtx context.Context) *tfLogSink {
17+
return &tfLogSink{
18+
tfCtx: tfCtx,
19+
}
20+
}
21+
22+
func (s *tfLogSink) LogEntry(ctx context.Context, e slog.SinkEntry) {
23+
var logFn func(ctx context.Context, msg string, additionalFields ...map[string]interface{})
24+
switch e.Level {
25+
case slog.LevelDebug:
26+
logFn = tflog.Debug
27+
case slog.LevelInfo:
28+
logFn = tflog.Info
29+
case slog.LevelWarn:
30+
logFn = tflog.Warn
31+
default:
32+
logFn = tflog.Error
33+
}
34+
logFn(s.tfCtx, e.Message, mapToFields(e.Fields))
35+
}
36+
37+
func (s *tfLogSink) Sync() {}
38+
39+
func mapToFields(m slog.Map) map[string]interface{} {
40+
fields := make(map[string]interface{}, len(m))
41+
for _, v := range m {
42+
fields[v.Name] = v.Value
43+
}
44+
return fields
45+
}

internal/provider/organization_data_source_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestAccOrganizationDataSource(t *testing.T) {
1919
t.Skip("Acceptance tests are disabled.")
2020
}
2121
ctx := context.Background()
22-
client := integration.StartCoder(ctx, t, "group_acc", true)
22+
client := integration.StartCoder(ctx, t, "org_data_acc", true)
2323
firstUser, err := client.User(ctx, codersdk.Me)
2424
require.NoError(t, err)
2525

internal/provider/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour
123123
return []func() resource.Resource{
124124
NewUserResource,
125125
NewGroupResource,
126+
NewTemplateResource,
126127
}
127128
}
128129

0 commit comments

Comments
 (0)