Skip to content

Commit 3209c04

Browse files
feat: add coderd_workspace_proxy resource (#53)
1 parent 0a7ce23 commit 3209c04

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

docs/resources/workspace_proxy.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coderd_workspace_proxy Resource - coderd"
4+
subcategory: ""
5+
description: |-
6+
A Workspace Proxy for the Coder deployment.
7+
---
8+
9+
# coderd_workspace_proxy (Resource)
10+
11+
A Workspace Proxy for the Coder deployment.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
21+
- `name` (String) Name of the workspace proxy.
22+
23+
### Optional
24+
25+
- `display_name` (String) Display name of the workspace proxy.
26+
27+
### Read-Only
28+
29+
- `id` (String) Workspace Proxy ID
30+
- `session_token` (String) Session token for the workspace proxy.

internal/provider/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour
124124
NewUserResource,
125125
NewGroupResource,
126126
NewTemplateResource,
127+
NewWorkspaceProxyResource,
127128
}
128129
}
129130

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/coder/coder/v2/codersdk"
8+
"github.com/hashicorp/terraform-plugin-framework/resource"
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
)
14+
15+
// Ensure provider defined types fully satisfy framework interfaces.
16+
var _ resource.Resource = &WorkspaceProxyResource{}
17+
18+
func NewWorkspaceProxyResource() resource.Resource {
19+
return &WorkspaceProxyResource{}
20+
}
21+
22+
// WorkspaceProxyResource defines the resource implementation.
23+
type WorkspaceProxyResource struct {
24+
data *CoderdProviderData
25+
}
26+
27+
// WorkspaceProxyResourceModel describes the resource data model.
28+
type WorkspaceProxyResourceModel struct {
29+
ID UUID `tfsdk:"id"`
30+
Name types.String `tfsdk:"name"`
31+
DisplayName types.String `tfsdk:"display_name"`
32+
Icon types.String `tfsdk:"icon"`
33+
SessionToken types.String `tfsdk:"session_token"`
34+
}
35+
36+
func (r *WorkspaceProxyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
37+
resp.TypeName = req.ProviderTypeName + "_workspace_proxy"
38+
}
39+
40+
func (r *WorkspaceProxyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
41+
resp.Schema = schema.Schema{
42+
MarkdownDescription: "A Workspace Proxy for the Coder deployment.",
43+
44+
Attributes: map[string]schema.Attribute{
45+
"id": schema.StringAttribute{
46+
CustomType: UUIDType,
47+
Computed: true,
48+
MarkdownDescription: "Workspace Proxy ID",
49+
PlanModifiers: []planmodifier.String{
50+
stringplanmodifier.UseStateForUnknown(),
51+
},
52+
},
53+
"name": schema.StringAttribute{
54+
MarkdownDescription: "Name of the workspace proxy.",
55+
Required: true,
56+
},
57+
"display_name": schema.StringAttribute{
58+
MarkdownDescription: "Display name of the workspace proxy.",
59+
Optional: true,
60+
Computed: true,
61+
},
62+
"icon": schema.StringAttribute{
63+
MarkdownDescription: "Relative path or external URL that specifes an icon to be displayed in the dashboard.",
64+
Required: true,
65+
},
66+
"session_token": schema.StringAttribute{
67+
MarkdownDescription: "Session token for the workspace proxy.",
68+
Computed: true,
69+
PlanModifiers: []planmodifier.String{
70+
stringplanmodifier.UseStateForUnknown(),
71+
},
72+
},
73+
},
74+
}
75+
}
76+
77+
func (r *WorkspaceProxyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
78+
// Prevent panic if the provider has not been configured.
79+
if req.ProviderData == nil {
80+
return
81+
}
82+
83+
data, ok := req.ProviderData.(*CoderdProviderData)
84+
85+
if !ok {
86+
resp.Diagnostics.AddError(
87+
"Unexpected Resource Configure Type",
88+
fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
89+
)
90+
91+
return
92+
}
93+
94+
r.data = data
95+
}
96+
97+
func (r *WorkspaceProxyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
98+
var data WorkspaceProxyResourceModel
99+
100+
// Read Terraform plan data into the model
101+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
102+
if resp.Diagnostics.HasError() {
103+
return
104+
}
105+
106+
client := r.data.Client
107+
wsp, err := client.CreateWorkspaceProxy(ctx, codersdk.CreateWorkspaceProxyRequest{
108+
Name: data.Name.ValueString(),
109+
DisplayName: data.DisplayName.ValueString(),
110+
Icon: data.Icon.ValueString(),
111+
})
112+
if err != nil {
113+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create workspace proxy: %v", err))
114+
return
115+
}
116+
117+
data.ID = UUIDValue(wsp.Proxy.ID)
118+
data.Name = types.StringValue(wsp.Proxy.Name)
119+
data.DisplayName = types.StringValue(wsp.Proxy.DisplayName)
120+
data.Icon = types.StringValue(wsp.Proxy.IconURL)
121+
data.SessionToken = types.StringValue(wsp.ProxyToken)
122+
123+
// Save data into Terraform state
124+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
125+
}
126+
127+
func (r *WorkspaceProxyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
128+
var data WorkspaceProxyResourceModel
129+
130+
// Read Terraform prior state data into the model
131+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
132+
133+
if resp.Diagnostics.HasError() {
134+
return
135+
}
136+
137+
client := r.data.Client
138+
wsp, err := client.WorkspaceProxyByID(ctx, data.ID.ValueUUID())
139+
if err != nil {
140+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to read workspace proxy: %v", err))
141+
return
142+
}
143+
144+
data.ID = UUIDValue(wsp.ID)
145+
data.Name = types.StringValue(wsp.Name)
146+
data.DisplayName = types.StringValue(wsp.DisplayName)
147+
data.Icon = types.StringValue(wsp.IconURL)
148+
149+
// Save updated data into Terraform state
150+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
151+
}
152+
153+
func (r *WorkspaceProxyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
154+
var data WorkspaceProxyResourceModel
155+
156+
// Read Terraform plan data into the model
157+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
158+
159+
if resp.Diagnostics.HasError() {
160+
return
161+
}
162+
163+
client := r.data.Client
164+
165+
wsp, err := client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
166+
ID: data.ID.ValueUUID(),
167+
Name: data.Name.ValueString(),
168+
DisplayName: data.DisplayName.ValueString(),
169+
Icon: data.Icon.ValueString(),
170+
RegenerateToken: false,
171+
})
172+
if err != nil {
173+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update workspace proxy: %v", err))
174+
return
175+
}
176+
177+
data.Name = types.StringValue(wsp.Proxy.Name)
178+
data.DisplayName = types.StringValue(wsp.Proxy.DisplayName)
179+
data.Icon = types.StringValue(wsp.Proxy.IconURL)
180+
181+
// Save updated data into Terraform state
182+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
183+
}
184+
185+
func (r *WorkspaceProxyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
186+
var data WorkspaceProxyResourceModel
187+
188+
// Read Terraform prior state data into the model
189+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
190+
191+
if resp.Diagnostics.HasError() {
192+
return
193+
}
194+
195+
client := r.data.Client
196+
err := client.DeleteWorkspaceProxyByID(ctx, data.ID.ValueUUID())
197+
if err != nil {
198+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete workspace proxy: %v", err))
199+
return
200+
}
201+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
"testing"
8+
"text/template"
9+
10+
"github.com/coder/terraform-provider-coderd/integration"
11+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestAccWorkspaceProxyResource(t *testing.T) {
16+
if os.Getenv("TF_ACC") == "" {
17+
t.Skip("Acceptance tests are disabled.")
18+
}
19+
ctx := context.Background()
20+
client := integration.StartCoder(ctx, t, "ws_proxy_acc", true)
21+
22+
cfg1 := testAccWorkspaceProxyResourceConfig{
23+
URL: client.URL.String(),
24+
Token: client.SessionToken(),
25+
Name: PtrTo("example"),
26+
DisplayName: PtrTo("Example WS Proxy"),
27+
Icon: PtrTo("/emojis/1f407.png"),
28+
}
29+
30+
cfg2 := cfg1
31+
cfg2.Name = PtrTo("example-new")
32+
cfg2.DisplayName = PtrTo("Example WS Proxy New")
33+
34+
resource.Test(t, resource.TestCase{
35+
IsUnitTest: true,
36+
PreCheck: func() { testAccPreCheck(t) },
37+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
38+
Steps: []resource.TestStep{
39+
// Create and Read testing
40+
{
41+
Config: cfg1.String(t),
42+
Check: resource.ComposeAggregateTestCheckFunc(
43+
resource.TestCheckResourceAttrSet("coderd_workspace_proxy.test", "session_token"),
44+
),
45+
},
46+
// Update and Read testing
47+
{
48+
Config: cfg2.String(t),
49+
Check: resource.ComposeAggregateTestCheckFunc(
50+
resource.TestCheckResourceAttrSet("coderd_workspace_proxy.test", "session_token")),
51+
},
52+
},
53+
})
54+
}
55+
56+
type testAccWorkspaceProxyResourceConfig struct {
57+
URL string
58+
Token string
59+
60+
Name *string
61+
DisplayName *string
62+
Icon *string
63+
}
64+
65+
func (c testAccWorkspaceProxyResourceConfig) String(t *testing.T) string {
66+
t.Helper()
67+
tpl := `
68+
provider coderd {
69+
url = "{{.URL}}"
70+
token = "{{.Token}}"
71+
}
72+
73+
resource "coderd_workspace_proxy" "test" {
74+
name = {{orNull .Name}}
75+
display_name = {{orNull .DisplayName}}
76+
icon = {{orNull .Icon}}
77+
}
78+
`
79+
// Define template functions
80+
funcMap := template.FuncMap{
81+
"orNull": PrintOrNull,
82+
}
83+
84+
buf := strings.Builder{}
85+
tmpl, err := template.New("test").Funcs(funcMap).Parse(tpl)
86+
require.NoError(t, err)
87+
88+
err = tmpl.Execute(&buf, c)
89+
require.NoError(t, err)
90+
91+
return buf.String()
92+
}

0 commit comments

Comments
 (0)