Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7645de8

Browse files
chore: support importing resources by fully qualified names (#84)
Previously, user, group and template resources could only be importedvia their UUIDs. The `coder/coder` frontend doesn't expose these UUIDsto the user, so we should provide an alternative, more user-friendly wayto import resources.User resources: ID or username, since usernames must be unique, evenacross multiple orgs.Template resources: ID or `<organization-name>/<template-name>`Group resources: ID or `<organization-name/<group-name>`
1 parent4d366f7 commit7645de8

File tree

9 files changed

+106
-13
lines changed

9 files changed

+106
-13
lines changed

‎docs/resources/group.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ subcategory: ""
55
description:|-
66
A group on the Coder deployment.
77
Creating groups requires an Enterprise license.
8+
When importing, the ID supplied can be either a group UUID retrieved via the API or <organization-name>/<group-name>.
89
---
910

1011
#coderd_group (Resource)
@@ -13,6 +14,8 @@ A group on the Coder deployment.
1314

1415
Creating groups requires an Enterprise license.
1516

17+
When importing, the ID supplied can be either a group UUID retrieved via the API or`<organization-name>/<group-name>`.
18+
1619
##Example Usage
1720

1821
```terraform

‎docs/resources/template.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ subcategory: ""
55
description:|-
66
A Coder template.
77
Logs from building template versions are streamed from the provisioner when the TF_LOG environment variable is INFO or higher.
8+
When importing, the ID supplied can be either a template UUID retrieved via the API or <organization-name>/<template-name>.
89
---
910

1011
#coderd_template (Resource)
@@ -13,6 +14,8 @@ A Coder template.
1314

1415
Logs from building template versions are streamed from the provisioner when the`TF_LOG` environment variable is`INFO` or higher.
1516

17+
When importing, the ID supplied can be either a template UUID retrieved via the API or`<organization-name>/<template-name>`.
18+
1619
##Example Usage
1720

1821
```terraform

‎docs/resources/user.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ page_title: "coderd_user Resource - terraform-provider-coderd"
44
subcategory:""
55
description:|-
66
A user on the Coder deployment.
7+
When importing, the ID supplied can be either a user UUID or a username.
78
---
89

910
#coderd_user (Resource)
1011

1112
A user on the Coder deployment.
1213

14+
When importing, the ID supplied can be either a user UUID or a username.
15+
1316
##Example Usage
1417

1518
```terraform

‎internal/provider/group_resource.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provider
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"github.com/coder/coder/v2/codersdk"
89
"github.com/google/uuid"
@@ -60,7 +61,9 @@ func (r *GroupResource) Metadata(ctx context.Context, req resource.MetadataReque
6061

6162
func (r*GroupResource)Schema(ctx context.Context,req resource.SchemaRequest,resp*resource.SchemaResponse) {
6263
resp.Schema= schema.Schema{
63-
MarkdownDescription:"A group on the Coder deployment.\n\nCreating groups requires an Enterprise license.",
64+
MarkdownDescription:"A group on the Coder deployment.\n\n"+
65+
"Creating groups requires an Enterprise license.\n\n"+
66+
"When importing, the ID supplied can be either a group UUID retrieved via the API or `<organization-name>/<group-name>`.",
6467

6568
Attributes:map[string]schema.Attribute{
6669
"id": schema.StringAttribute{
@@ -324,10 +327,30 @@ func (r *GroupResource) Delete(ctx context.Context, req resource.DeleteRequest,
324327
}
325328

326329
func (r*GroupResource)ImportState(ctx context.Context,req resource.ImportStateRequest,resp*resource.ImportStateResponse) {
330+
vargroupID uuid.UUID
327331
client:=r.data.Client
328-
groupID,err:=uuid.Parse(req.ID)
329-
iferr!=nil {
330-
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Unable to parse import group ID as UUID, got error: %s",err))
332+
idParts:=strings.Split(req.ID,"/")
333+
iflen(idParts)==1 {
334+
varerrerror
335+
groupID,err=uuid.Parse(req.ID)
336+
iferr!=nil {
337+
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Unable to parse import group ID as UUID, got error: %s",err))
338+
return
339+
}
340+
}elseiflen(idParts)==2 {
341+
org,err:=client.OrganizationByName(ctx,idParts[0])
342+
iferr!=nil {
343+
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Failed to get organization with name %s: %s",idParts[0],err))
344+
return
345+
}
346+
group,err:=client.GroupByOrgAndName(ctx,org.ID,idParts[1])
347+
iferr!=nil {
348+
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Failed to get group with name %s: %s",idParts[1],err))
349+
return
350+
}
351+
groupID=group.ID
352+
}else {
353+
resp.Diagnostics.AddError("Client Error","Invalid import ID format, expected a single UUID or `<organization-name>/<group-name>`")
331354
return
332355
}
333356
group,err:=client.Group(ctx,groupID)
@@ -339,5 +362,5 @@ func (r *GroupResource) ImportState(ctx context.Context, req resource.ImportStat
339362
resp.Diagnostics.AddError("Client Error","Cannot import groups created via OIDC")
340363
return
341364
}
342-
resource.ImportStatePassthroughID(ctx,path.Root("id"),req,resp)
365+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx,path.Root("id"),groupID.String())...)
343366
}

‎internal/provider/group_resource_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,21 @@ func TestAccGroupResource(t *testing.T) {
7878
resource.TestCheckResourceAttr("coderd_group.test","members.0",user1.ID.String()),
7979
),
8080
},
81-
// Import
81+
// Import by ID
8282
{
83-
Config:cfg1.String(t),
8483
ResourceName:"coderd_group.test",
8584
ImportState:true,
8685
ImportStateVerify:true,
8786
ImportStateVerifyIgnore: []string{"members"},
8887
},
88+
// Import by org name and group name
89+
{
90+
ResourceName:"coderd_group.test",
91+
ImportState:true,
92+
ImportStateId:"default/example-group",
93+
ImportStateVerify:true,
94+
ImportStateVerifyIgnore: []string{"members"},
95+
},
8996
// Update and Read
9097
{
9198
Config:cfg2.String(t),

‎internal/provider/template_resource.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"strings"
910

1011
"cdr.dev/slog"
1112
"github.com/coder/coder/v2/codersdk"
@@ -230,7 +231,8 @@ func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRe
230231
func (r*TemplateResource)Schema(ctx context.Context,req resource.SchemaRequest,resp*resource.SchemaResponse) {
231232
resp.Schema= schema.Schema{
232233
MarkdownDescription:"A Coder template.\n\nLogs from building template versions are streamed from the provisioner "+
233-
"when the `TF_LOG` environment variable is `INFO` or higher.",
234+
"when the `TF_LOG` environment variable is `INFO` or higher.\n\n"+
235+
"When importing, the ID supplied can be either a template UUID retrieved via the API or `<organization-name>/<template-name>`.",
234236

235237
Attributes:map[string]schema.Attribute{
236238
"id": schema.StringAttribute{
@@ -771,7 +773,28 @@ func (r *TemplateResource) Delete(ctx context.Context, req resource.DeleteReques
771773
}
772774

773775
func (r*TemplateResource)ImportState(ctx context.Context,req resource.ImportStateRequest,resp*resource.ImportStateResponse) {
774-
resource.ImportStatePassthroughID(ctx,path.Root("id"),req,resp)
776+
idParts:=strings.Split(req.ID,"/")
777+
iflen(idParts)==1 {
778+
resource.ImportStatePassthroughID(ctx,path.Root("id"),req,resp)
779+
return
780+
}elseiflen(idParts)==2 {
781+
client:=r.data.Client
782+
org,err:=client.OrganizationByName(ctx,idParts[0])
783+
iferr!=nil {
784+
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Failed to get organization with name %s: %s",idParts[0],err))
785+
return
786+
}
787+
template,err:=client.TemplateByName(ctx,org.ID,idParts[1])
788+
iferr!=nil {
789+
resp.Diagnostics.AddError("Client Error",fmt.Sprintf("Failed to get template with name %s: %s",idParts[1],err))
790+
return
791+
}
792+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx,path.Root("id"),template.ID.String())...)
793+
return
794+
}else {
795+
resp.Diagnostics.AddError("Client Error","Invalid import ID format, expected a single UUID or `<organization-name>/<template-name>`")
796+
return
797+
}
775798
}
776799

777800
// ConfigValidators implements resource.ResourceWithConfigValidators.

‎internal/provider/template_resource_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func TestAccTemplateResource(t *testing.T) {
145145
},
146146
Check:testAccCheckNumTemplateVersions(ctx,client,3),
147147
},
148-
// Import
148+
// Import by ID
149149
{
150150
Config:cfg1.String(t),
151151
ResourceName:"coderd_template.test",
@@ -155,6 +155,14 @@ func TestAccTemplateResource(t *testing.T) {
155155
// We can't import ACL as we can't currently differentiate between managed and unmanaged ACL
156156
ImportStateVerifyIgnore: []string{"versions","acl"},
157157
},
158+
// Import by org name and template name
159+
{
160+
ResourceName:"coderd_template.test",
161+
ImportState:true,
162+
ImportStateVerify:true,
163+
ImportStateId:"default/example-template",
164+
ImportStateVerifyIgnore: []string{"versions","acl"},
165+
},
158166
// Change existing version directory & name, update template metadata. Creates a fourth version.
159167
{
160168
Config:cfg2.String(t),

‎internal/provider/user_resource.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/google/uuid"
89
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
910
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1011
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -55,7 +56,8 @@ func (r *UserResource) Metadata(ctx context.Context, req resource.MetadataReques
5556

5657
func (r*UserResource)Schema(ctx context.Context,req resource.SchemaRequest,resp*resource.SchemaResponse) {
5758
resp.Schema= schema.Schema{
58-
MarkdownDescription:"A user on the Coder deployment.",
59+
MarkdownDescription:"A user on the Coder deployment.\n\n"+
60+
"When importing, the ID supplied can be either a user UUID or a username.",
5961

6062
Attributes:map[string]schema.Attribute{
6163
"id": schema.StringAttribute{
@@ -371,6 +373,18 @@ func (r *UserResource) Delete(ctx context.Context, req resource.DeleteRequest, r
371373
tflog.Info(ctx,"successfully deleted user")
372374
}
373375

376+
// Req.ID can be either a UUID or a username.
374377
func (r*UserResource)ImportState(ctx context.Context,req resource.ImportStateRequest,resp*resource.ImportStateResponse) {
375-
resource.ImportStatePassthroughID(ctx,path.Root("id"),req,resp)
378+
_,err:=uuid.Parse(req.ID)
379+
iferr==nil {
380+
resource.ImportStatePassthroughID(ctx,path.Root("id"),req,resp)
381+
return
382+
}
383+
client:=r.data.Client
384+
user,err:=client.User(ctx,req.ID)
385+
iferr!=nil {
386+
resp.Diagnostics.AddError("Client Error","Invalid import ID format, expected a single UUID or a valid username")
387+
return
388+
}
389+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx,path.Root("id"),user.ID.String())...)
376390
}

‎internal/provider/user_resource_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,23 @@ func TestAccUserResource(t *testing.T) {
6060
resource.TestCheckResourceAttr("coderd_user.test","suspended","false"),
6161
),
6262
},
63-
//ImportState testing
63+
//Import by ID
6464
{
6565
ResourceName:"coderd_user.test",
6666
ImportState:true,
6767
ImportStateVerify:true,
6868
// We can't pull the password from the API.
6969
ImportStateVerifyIgnore: []string{"password"},
7070
},
71+
// ImportState by username
72+
{
73+
ResourceName:"coderd_user.test",
74+
ImportState:true,
75+
ImportStateVerify:true,
76+
ImportStateId:"example",
77+
// We can't pull the password from the API.
78+
ImportStateVerifyIgnore: []string{"password"},
79+
},
7180
// Update and Read testing
7281
{
7382
Config:cfg2.String(t),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp