Skip to content

Commit 94f4be2

Browse files
committed
feat: add coderd_workspace_proxy resource
1 parent 9b0c900 commit 94f4be2

File tree

5 files changed

+355
-4
lines changed

5 files changed

+355
-4
lines changed

docs/data-sources/template.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
page_title: "coderd_template Data Source - coderd"
44
subcategory: ""
55
description: |-
6-
An existing template on the coder deployment
6+
An existing template on the Coder deployment.
77
---
88

99
# coderd_template (Data Source)
1010

11-
An existing template on the coder deployment
11+
An existing template on the Coder deployment.
1212

1313

1414

@@ -19,7 +19,7 @@ An existing template on the coder deployment
1919

2020
- `id` (String) The ID of the template to retrieve. This field will be populated if a template name is supplied.
2121
- `name` (String) The name of the template to retrieve. This field will be populated if an ID is supplied.
22-
- `organization_id` (String) ID of the organization the template is associated with.
22+
- `organization_id` (String) ID of the organization the template is associated with. This field will be populated if an ID is supplied. Defaults to the provider default organization ID.
2323

2424
### Read-Only
2525

@@ -38,7 +38,7 @@ An existing template on the coder deployment
3838
- `display_name` (String) Display name of the template.
3939
- `failure_ttl_ms` (Number) Automatic cleanup TTL for failed workspace builds.
4040
- `icon` (String) URL of the template's icon.
41-
- `require_active_version` (Boolean) Whether workspaces created from the template must be up-to-datae on the latest active version.
41+
- `require_active_version` (Boolean) Whether workspaces created from the template must be up-to-date on the latest active version.
4242
- `time_til_dormant_autodelete_ms` (Number) Duration of inactivity after the workspace becomes dormant before a workspace is automatically deleted.
4343
- `time_til_dormant_ms` (Number) Duration of inactivity before a workspace is considered dormant.
4444
- `updated_at` (Number) Unix timestamp of when the template was last updated.

docs/resources/workspace_proxy.md

Lines changed: 30 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy