Thanks to visit codestin.com
Credit goes to github.com

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

+4-4
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

+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,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+
}
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)