Skip to content

Commit

Permalink
chore: add uuid terraform type
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Jul 25, 2024
1 parent 37c1724 commit b03a307
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 125 deletions.
35 changes: 14 additions & 21 deletions internal/provider/group_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/coder/coder/v2/codersdk"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
Expand All @@ -29,9 +28,9 @@ type GroupDataSource struct {
// GroupDataSourceModel describes the data source data model.
type GroupDataSourceModel struct {
// ID or name and organization ID must be set
ID types.String `tfsdk:"id"`
ID UUID `tfsdk:"id"`
Name types.String `tfsdk:"name"`
OrganizationID types.String `tfsdk:"organization_id"`
OrganizationID UUID `tfsdk:"organization_id"`

DisplayName types.String `tfsdk:"display_name"`
AvatarURL types.String `tfsdk:"avatar_url"`
Expand All @@ -41,7 +40,7 @@ type GroupDataSourceModel struct {
}

type Member struct {
ID types.String `tfsdk:"id"`
ID UUID `tfsdk:"id"`
Username types.String `tfsdk:"username"`
Email types.String `tfsdk:"email"`
CreatedAt types.Int64 `tfsdk:"created_at"`
Expand All @@ -64,6 +63,7 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque
MarkdownDescription: "The ID of the group to retrieve. This field will be populated if a name and organization ID is supplied.",
Optional: true,
Computed: true,
CustomType: UUIDType{},
Validators: []validator.String{
stringvalidator.AtLeastOneOf(path.Expressions{
path.MatchRoot("name"),
Expand All @@ -78,6 +78,7 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque
},
"organization_id": schema.StringAttribute{
MarkdownDescription: "The organization ID that the group belongs to. This field will be populated if an ID is supplied. Defaults to the provider default organization ID.",
CustomType: UUIDType{},
Optional: true,
Computed: true,
},
Expand All @@ -101,7 +102,8 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
CustomType: UUIDType{},
Computed: true,
},
"username": schema.StringAttribute{
Computed: true,
Expand Down Expand Up @@ -169,36 +171,27 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest,
client := d.data.Client

if data.OrganizationID.IsNull() {
data.OrganizationID = types.StringValue(d.data.DefaultOrganizationID)
data.OrganizationID = UUIDValue(d.data.DefaultOrganizationID)
}

var group codersdk.Group
var err error
if !data.ID.IsNull() {
groupID, err := uuid.Parse(data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied group ID as UUID, got error: %s", err))
return
}

groupID := data.ID.ValueUUID()
group, err = client.Group(ctx, groupID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group by ID, got error: %s", err))
return
}
data.Name = types.StringValue(group.Name)
data.OrganizationID = types.StringValue(group.OrganizationID.String())
data.OrganizationID = UUIDValue(group.OrganizationID)
} else {
orgID, err := uuid.Parse(data.OrganizationID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied organization ID as UUID, got error: %s", err))
return
}
group, err = client.GroupByOrgAndName(ctx, orgID, data.Name.ValueString())
group, err = client.GroupByOrgAndName(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString())
if err != nil {
resp.Diagnostics.AddError("Failed to get group by name and org ID", err.Error())
return
}
data.ID = types.StringValue(group.ID.String())
data.ID = UUIDValue(group.ID)
}

data.DisplayName = types.StringValue(group.DisplayName)
Expand All @@ -207,7 +200,7 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest,
members := make([]Member, 0, len(group.Members))
for _, member := range group.Members {
members = append(members, Member{
ID: types.StringValue(member.ID.String()),
ID: UUIDValue(member.ID),
Username: types.StringValue(member.Username),
Email: types.StringValue(member.Email),
CreatedAt: types.Int64Value(member.CreatedAt.Unix()),
Expand Down
40 changes: 13 additions & 27 deletions internal/provider/group_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ type GroupResource struct {

// GroupResourceModel describes the resource data model.
type GroupResourceModel struct {
ID types.String `tfsdk:"id"`
ID UUID `tfsdk:"id"`

Name types.String `tfsdk:"name"`
DisplayName types.String `tfsdk:"display_name"`
AvatarURL types.String `tfsdk:"avatar_url"`
QuotaAllowance types.Int32 `tfsdk:"quota_allowance"`
OrganizationID types.String `tfsdk:"organization_id"`
OrganizationID UUID `tfsdk:"organization_id"`
Members types.Set `tfsdk:"members"`
}

Expand All @@ -56,6 +56,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: "Group ID.",
CustomType: UUIDType{},
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
Expand Down Expand Up @@ -84,6 +85,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
},
"organization_id": schema.StringAttribute{
MarkdownDescription: "The organization ID that the group belongs to. Defaults to the provider default organization ID.",
CustomType: UUIDType{},
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
Expand Down Expand Up @@ -132,14 +134,10 @@ func (r *GroupResource) Create(ctx context.Context, req resource.CreateRequest,
client := r.data.Client

if data.OrganizationID.IsUnknown() {
data.OrganizationID = types.StringValue(r.data.DefaultOrganizationID)
data.OrganizationID = UUIDValue(r.data.DefaultOrganizationID)
}

orgID, err := uuid.Parse(data.OrganizationID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied organization ID as UUID, got error: %s", err))
return
}
orgID := data.OrganizationID.ValueUUID()

displayName := data.Name.ValueString()
if data.DisplayName.ValueString() != "" {
Expand All @@ -160,7 +158,7 @@ func (r *GroupResource) Create(ctx context.Context, req resource.CreateRequest,
tflog.Trace(ctx, "successfully created group", map[string]any{
"id": group.ID.String(),
})
data.ID = types.StringValue(group.ID.String())
data.ID = UUIDValue(group.ID)
data.DisplayName = types.StringValue(group.DisplayName)

tflog.Trace(ctx, "setting group members")
Expand Down Expand Up @@ -196,11 +194,7 @@ func (r *GroupResource) Read(ctx context.Context, req resource.ReadRequest, resp

client := r.data.Client

groupID, err := uuid.Parse(data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied group ID as UUID, got error: %s", err))
return
}
groupID := data.ID.ValueUUID()

group, err := client.Group(ctx, groupID)
if err != nil {
Expand All @@ -212,7 +206,7 @@ func (r *GroupResource) Read(ctx context.Context, req resource.ReadRequest, resp
data.DisplayName = types.StringValue(group.DisplayName)
data.AvatarURL = types.StringValue(group.AvatarURL)
data.QuotaAllowance = types.Int32Value(int32(group.QuotaAllowance))
data.OrganizationID = types.StringValue(group.OrganizationID.String())
data.OrganizationID = UUIDValue(group.OrganizationID)
if !data.Members.IsNull() {
members := make([]attr.Value, 0, len(group.Members))
for _, member := range group.Members {
Expand All @@ -237,13 +231,9 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest,

client := r.data.Client
if data.OrganizationID.IsUnknown() {
data.OrganizationID = types.StringValue(r.data.DefaultOrganizationID)
}
groupID, err := uuid.Parse(data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied group ID as UUID, got error: %s", err))
return
data.OrganizationID = UUIDValue(r.data.DefaultOrganizationID)
}
groupID := data.ID.ValueUUID()

group, err := client.Group(ctx, groupID)
if err != nil {
Expand Down Expand Up @@ -301,14 +291,10 @@ func (r *GroupResource) Delete(ctx context.Context, req resource.DeleteRequest,
}

client := r.data.Client
groupID, err := uuid.Parse(data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied group ID as UUID, got error: %s", err))
return
}
groupID := data.ID.ValueUUID()

tflog.Trace(ctx, "deleting group")
err = client.DeleteGroup(ctx, groupID)
err := client.DeleteGroup(ctx, groupID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete group, got error: %s", err))
return
Expand Down
17 changes: 6 additions & 11 deletions internal/provider/organization_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/coder/coder/v2/codersdk"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
Expand All @@ -30,7 +29,7 @@ type OrganizationDataSource struct {
// OrganizationDataSourceModel describes the data source data model.
type OrganizationDataSourceModel struct {
// Exactly one of ID, IsDefault, or Name must be set.
ID types.String `tfsdk:"id"`
ID UUID `tfsdk:"id"`
IsDefault types.Bool `tfsdk:"is_default"`
Name types.String `tfsdk:"name"`

Expand All @@ -52,6 +51,7 @@ func (d *OrganizationDataSource) Schema(ctx context.Context, req datasource.Sche
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: "The ID of the organization to retrieve. This field will be populated if the organization is found by name, or if the default organization is requested.",
CustomType: UUIDType{},
Optional: true,
Computed: true,
},
Expand Down Expand Up @@ -116,23 +116,19 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe
client := d.data.Client

var org codersdk.Organization
var err error
if !data.ID.IsNull() { // By ID
orgID, err := uuid.Parse(data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse supplied ID as UUID, got error: %s", err))
return
}
orgID := data.ID.ValueUUID()
org, err = client.Organization(ctx, orgID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by ID, got error: %s", err))
return
}
if org.ID.String() != data.ID.ValueString() {
if org.ID != data.ID.ValueUUID() {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Organization ID %s does not match requested ID %s", org.ID, data.ID))
return
}
} else if data.IsDefault.ValueBool() { // Get Default
var err error
org, err = client.OrganizationByName(ctx, "default")
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get default organization, got error: %s", err))
Expand All @@ -143,7 +139,6 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe
return
}
} else { // By Name
var err error
org, err = client.OrganizationByName(ctx, data.Name.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by name, got error: %s", err))
Expand All @@ -154,7 +149,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe
return
}
}
data.ID = types.StringValue(org.ID.String())
data.ID = UUIDValue(org.ID)
data.Name = types.StringValue(org.Name)
data.IsDefault = types.BoolValue(org.IsDefault)
data.CreatedAt = types.Int64Value(org.CreatedAt.Unix())
Expand Down
14 changes: 7 additions & 7 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"cdr.dev/slog"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/provider"
Expand All @@ -31,18 +32,16 @@ type CoderdProvider struct {
}

type CoderdProviderData struct {
Client *codersdk.Client
// TODO(ethanndickson): We should use a custom TFPF type for UUIDs everywhere
// possible, instead of `string` and `types.String`.
DefaultOrganizationID string
Client *codersdk.Client
DefaultOrganizationID uuid.UUID
}

// CoderdProviderModel describes the provider data model.
type CoderdProviderModel struct {
URL types.String `tfsdk:"url"`
Token types.String `tfsdk:"token"`

DefaultOrganizationID types.String `tfsdk:"default_organization_id"`
DefaultOrganizationID UUID `tfsdk:"default_organization_id"`
}

func (p *CoderdProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
Expand All @@ -63,6 +62,7 @@ func (p *CoderdProvider) Schema(ctx context.Context, req provider.SchemaRequest,
},
"default_organization_id": schema.StringAttribute{
MarkdownDescription: "Default organization ID to use when creating resources. Defaults to the first organization the token has access to.",
CustomType: UUIDType{},
Optional: true,
},
},
Expand Down Expand Up @@ -109,11 +109,11 @@ func (p *CoderdProvider) Configure(ctx context.Context, req provider.ConfigureRe
resp.Diagnostics.AddError("default_organization_id", "failed to get default organization ID: "+err.Error())
return
}
data.DefaultOrganizationID = types.StringValue(user.OrganizationIDs[0].String())
data.DefaultOrganizationID = UUIDValue(user.OrganizationIDs[0])
}
providerData := &CoderdProviderData{
Client: client,
DefaultOrganizationID: data.DefaultOrganizationID.ValueString(),
DefaultOrganizationID: data.DefaultOrganizationID.ValueUUID(),
}
resp.DataSourceData = providerData
resp.ResourceData = providerData
Expand Down
Loading

0 comments on commit b03a307

Please sign in to comment.