From 4aa400fb686e3496115b65c0dbadd6e2e7323f98 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Thu, 7 Nov 2024 22:33:21 +0000 Subject: [PATCH] maybe --- internal/{provider => }/logger.go | 20 ++-- internal/provider/group_data_source.go | 41 ++++---- internal/provider/group_data_source_test.go | 3 +- internal/provider/group_resource.go | 42 ++++----- internal/provider/group_resource_test.go | 2 +- internal/provider/license_resource_test.go | 2 +- internal/provider/organization_data_source.go | 23 ++--- .../provider/organization_data_source_test.go | 2 +- internal/provider/organization_resource.go | 94 +++++++++++++++++++ internal/provider/provider.go | 4 +- internal/provider/template_data_source.go | 45 ++++----- .../provider/template_data_source_test.go | 2 +- internal/provider/template_resource.go | 91 +++++++++--------- internal/provider/template_resource_test.go | 6 +- internal/provider/user_data_source.go | 17 ++-- internal/provider/user_data_source_test.go | 2 +- internal/provider/user_resource.go | 14 +-- internal/provider/user_resource_test.go | 2 +- internal/provider/workspace_proxy_resource.go | 2 +- .../provider/workspace_proxy_resource_test.go | 2 +- internal/{provider => }/util.go | 41 ++++---- internal/{provider => }/uuid.go | 6 +- internal/{provider => }/uuid_internal_test.go | 2 +- 23 files changed, 287 insertions(+), 178 deletions(-) rename internal/{provider => }/logger.go (63%) create mode 100644 internal/provider/organization_resource.go rename internal/{provider => }/util.go (78%) rename internal/{provider => }/uuid.go (94%) rename internal/{provider => }/uuid_internal_test.go (99%) diff --git a/internal/provider/logger.go b/internal/logger.go similarity index 63% rename from internal/provider/logger.go rename to internal/logger.go index a17fc65..59706fc 100644 --- a/internal/provider/logger.go +++ b/internal/logger.go @@ -1,4 +1,4 @@ -package provider +package internal import ( "context" @@ -7,19 +7,19 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -var _ slog.Sink = &tfLogSink{} +var _ slog.Sink = &tflogSink{} -type tfLogSink struct { - tfCtx context.Context +type tflogSink struct { + ctx context.Context } -func newTFLogSink(tfCtx context.Context) *tfLogSink { - return &tfLogSink{ - tfCtx: tfCtx, +func NewLogSink(ctx context.Context) slog.Sink { + return &tflogSink{ + ctx: ctx, } } -func (s *tfLogSink) LogEntry(ctx context.Context, e slog.SinkEntry) { +func (s *tflogSink) LogEntry(ctx context.Context, e slog.SinkEntry) { var logFn func(ctx context.Context, msg string, additionalFields ...map[string]interface{}) switch e.Level { case slog.LevelDebug: @@ -31,10 +31,10 @@ func (s *tfLogSink) LogEntry(ctx context.Context, e slog.SinkEntry) { default: logFn = tflog.Error } - logFn(s.tfCtx, e.Message, mapToFields(e.Fields)) + logFn(s.ctx, e.Message, mapToFields(e.Fields)) } -func (s *tfLogSink) Sync() {} +func (s *tflogSink) Sync() {} func mapToFields(m slog.Map) map[string]interface{} { fields := make(map[string]interface{}, len(m)) diff --git a/internal/provider/group_data_source.go b/internal/provider/group_data_source.go index 007abaa..0412e42 100644 --- a/internal/provider/group_data_source.go +++ b/internal/provider/group_data_source.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -28,9 +29,9 @@ type GroupDataSource struct { // GroupDataSourceModel describes the data source data model. type GroupDataSourceModel struct { // ID or name and organization ID must be set - ID UUID `tfsdk:"id"` - Name types.String `tfsdk:"name"` - OrganizationID UUID `tfsdk:"organization_id"` + ID internal.UUID `tfsdk:"id"` + Name types.String `tfsdk:"name"` + OrganizationID internal.UUID `tfsdk:"organization_id"` DisplayName types.String `tfsdk:"display_name"` AvatarURL types.String `tfsdk:"avatar_url"` @@ -40,14 +41,14 @@ type GroupDataSourceModel struct { } type Member struct { - ID UUID `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Email types.String `tfsdk:"email"` - CreatedAt types.Int64 `tfsdk:"created_at"` - LastSeenAt types.Int64 `tfsdk:"last_seen_at"` - Status types.String `tfsdk:"status"` - LoginType types.String `tfsdk:"login_type"` - ThemePreference types.String `tfsdk:"theme_preference"` + ID internal.UUID `tfsdk:"id"` + Username types.String `tfsdk:"username"` + Email types.String `tfsdk:"email"` + CreatedAt types.Int64 `tfsdk:"created_at"` + LastSeenAt types.Int64 `tfsdk:"last_seen_at"` + Status types.String `tfsdk:"status"` + LoginType types.String `tfsdk:"login_type"` + ThemePreference types.String `tfsdk:"theme_preference"` } func (d *GroupDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -63,7 +64,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, + CustomType: internal.UUIDType, Validators: []validator.String{ stringvalidator.AtLeastOneOf(path.Expressions{ path.MatchRoot("name"), @@ -78,7 +79,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, + CustomType: internal.UUIDType, Optional: true, Computed: true, }, @@ -102,7 +103,7 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, }, "username": schema.StringAttribute{ @@ -176,7 +177,7 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, client := d.data.Client if data.OrganizationID.IsNull() { - data.OrganizationID = UUIDValue(d.data.DefaultOrganizationID) + data.OrganizationID = internal.UUIDValue(d.data.DefaultOrganizationID) } var ( @@ -187,7 +188,7 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, groupID := data.ID.ValueUUID() group, err = client.Group(ctx, groupID) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with ID %s not found. Marking as deleted.", groupID.String())) resp.State.RemoveResource(ctx) return @@ -196,11 +197,11 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } data.Name = types.StringValue(group.Name) - data.OrganizationID = UUIDValue(group.OrganizationID) + data.OrganizationID = internal.UUIDValue(group.OrganizationID) } else { group, err = client.GroupByOrgAndName(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString()) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with name %s not found in organization with ID %s. Marking as deleted.", data.Name.ValueString(), data.OrganizationID.ValueString())) resp.State.RemoveResource(ctx) return @@ -208,7 +209,7 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp.Diagnostics.AddError("Failed to get group by name and org ID", err.Error()) return } - data.ID = UUIDValue(group.ID) + data.ID = internal.UUIDValue(group.ID) } data.DisplayName = types.StringValue(group.DisplayName) @@ -217,7 +218,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: UUIDValue(member.ID), + ID: internal.UUIDValue(member.ID), Username: types.StringValue(member.Username), Email: types.StringValue(member.Email), CreatedAt: types.Int64Value(member.CreatedAt.Unix()), diff --git a/internal/provider/group_data_source_test.go b/internal/provider/group_data_source_test.go index 5a081af..7ac42c2 100644 --- a/internal/provider/group_data_source_test.go +++ b/internal/provider/group_data_source_test.go @@ -11,6 +11,7 @@ import ( "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" + "github.com/coder/terraform-provider-coderd/internal" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/require" ) @@ -188,7 +189,7 @@ data "coderd_group" "test" { ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": internal.PrintOrNull, } buf := strings.Builder{} diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go index fa370a0..954034d 100644 --- a/internal/provider/group_resource.go +++ b/internal/provider/group_resource.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -37,14 +38,14 @@ type GroupResource struct { // GroupResourceModel describes the resource data model. type GroupResourceModel struct { - 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 UUID `tfsdk:"organization_id"` - Members types.Set `tfsdk:"members"` + ID internal.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 internal.UUID `tfsdk:"organization_id"` + Members types.Set `tfsdk:"members"` } func CheckGroupEntitlements(ctx context.Context, features map[codersdk.FeatureName]codersdk.Feature) (diags diag.Diagnostics) { @@ -67,7 +68,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "Group ID.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), @@ -104,7 +105,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, + CustomType: internal.UUIDType, Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ @@ -113,7 +114,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, }, "members": schema.SetAttribute{ MarkdownDescription: "Members of the group, by ID. If `null`, members will not be added or removed by Terraform. To have a group resource with unmanaged members, but be able to read the members in Terraform, use `data.coderd_group`", - ElementType: UUIDType, + ElementType: internal.UUIDType, Optional: true, }, }, @@ -141,9 +142,8 @@ func (r *GroupResource) Configure(ctx context.Context, req resource.ConfigureReq } func (r *GroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data GroupResourceModel - // Read Terraform plan data into the model + var data GroupResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -158,7 +158,7 @@ func (r *GroupResource) Create(ctx context.Context, req resource.CreateRequest, client := r.data.Client if data.OrganizationID.IsUnknown() { - data.OrganizationID = UUIDValue(r.data.DefaultOrganizationID) + data.OrganizationID = internal.UUIDValue(r.data.DefaultOrganizationID) } orgID := data.OrganizationID.ValueUUID() @@ -177,7 +177,7 @@ func (r *GroupResource) Create(ctx context.Context, req resource.CreateRequest, tflog.Info(ctx, "successfully created group", map[string]any{ "id": group.ID.String(), }) - data.ID = UUIDValue(group.ID) + data.ID = internal.UUIDValue(group.ID) data.DisplayName = types.StringValue(group.DisplayName) tflog.Info(ctx, "setting group members") @@ -217,7 +217,7 @@ func (r *GroupResource) Read(ctx context.Context, req resource.ReadRequest, resp group, err := client.Group(ctx, groupID) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with ID %s not found. Marking as deleted.", groupID.String())) resp.State.RemoveResource(ctx) return @@ -230,13 +230,13 @@ 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 = UUIDValue(group.OrganizationID) + data.OrganizationID = internal.UUIDValue(group.OrganizationID) if !data.Members.IsNull() { members := make([]attr.Value, 0, len(group.Members)) for _, member := range group.Members { - members = append(members, UUIDValue(member.ID)) + members = append(members, internal.UUIDValue(member.ID)) } - data.Members = types.SetValueMust(UUIDType, members) + data.Members = types.SetValueMust(internal.UUIDType, members) } // Save updated data into Terraform state @@ -255,7 +255,7 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest, client := r.data.Client if data.OrganizationID.IsUnknown() { - data.OrganizationID = UUIDValue(r.data.DefaultOrganizationID) + data.OrganizationID = internal.UUIDValue(r.data.DefaultOrganizationID) } groupID := data.ID.ValueUUID() @@ -267,7 +267,7 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest, var add []string var remove []string if !data.Members.IsNull() { - var plannedMembers []UUID + var plannedMembers []internal.UUID resp.Diagnostics.Append( data.Members.ElementsAs(ctx, &plannedMembers, false)..., ) diff --git a/internal/provider/group_resource_test.go b/internal/provider/group_resource_test.go index 865851a..aeec502 100644 --- a/internal/provider/group_resource_test.go +++ b/internal/provider/group_resource_test.go @@ -195,7 +195,7 @@ resource "coderd_group" "test" { } ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/license_resource_test.go b/internal/provider/license_resource_test.go index e2d13d6..a7f30f3 100644 --- a/internal/provider/license_resource_test.go +++ b/internal/provider/license_resource_test.go @@ -61,7 +61,7 @@ resource "coderd_license" "test" { } ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/organization_data_source.go b/internal/provider/organization_data_source.go index 835a2ed..4cc7fcb 100644 --- a/internal/provider/organization_data_source.go +++ b/internal/provider/organization_data_source.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -29,9 +30,9 @@ type OrganizationDataSource struct { // OrganizationDataSourceModel describes the data source data model. type OrganizationDataSourceModel struct { // Exactly one of ID, IsDefault, or Name must be set. - ID UUID `tfsdk:"id"` - IsDefault types.Bool `tfsdk:"is_default"` - Name types.String `tfsdk:"name"` + ID internal.UUID `tfsdk:"id"` + IsDefault types.Bool `tfsdk:"is_default"` + Name types.String `tfsdk:"name"` CreatedAt types.Int64 `tfsdk:"created_at"` UpdatedAt types.Int64 `tfsdk:"updated_at"` @@ -55,7 +56,7 @@ This data source is only compatible with Coder version [2.13.0](https://github.c 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, + CustomType: internal.UUIDType, Optional: true, Computed: true, }, @@ -81,7 +82,7 @@ This data source is only compatible with Coder version [2.13.0](https://github.c "members": schema.SetAttribute{ MarkdownDescription: "Members of the organization, by ID", Computed: true, - ElementType: UUIDType, + ElementType: internal.UUIDType, }, }, } @@ -127,7 +128,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe orgID := data.ID.ValueUUID() org, err = client.Organization(ctx, orgID) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Organization with ID %s not found. Marking as deleted.", data.ID.ValueString())) resp.State.RemoveResource(ctx) return @@ -142,7 +143,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } else if data.IsDefault.ValueBool() { // Get Default org, err = client.OrganizationByName(ctx, "default") if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", "Default organization not found. Marking as deleted.") resp.State.RemoveResource(ctx) return @@ -157,7 +158,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } else { // By Name org, err = client.OrganizationByName(ctx, data.Name.ValueString()) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Organization with name %s not found. Marking as deleted.", data.Name)) resp.State.RemoveResource(ctx) return @@ -170,7 +171,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe return } } - data.ID = UUIDValue(org.ID) + data.ID = internal.UUIDValue(org.ID) data.Name = types.StringValue(org.Name) data.IsDefault = types.BoolValue(org.IsDefault) data.CreatedAt = types.Int64Value(org.CreatedAt.Unix()) @@ -182,9 +183,9 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } memberIDs := make([]attr.Value, 0, len(members)) for _, member := range members { - memberIDs = append(memberIDs, UUIDValue(member.UserID)) + memberIDs = append(memberIDs, internal.UUIDValue(member.UserID)) } - data.Members = types.SetValueMust(UUIDType, memberIDs) + data.Members = types.SetValueMust(internal.UUIDType, memberIDs) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go index c7bb982..0213704 100644 --- a/internal/provider/organization_data_source_test.go +++ b/internal/provider/organization_data_source_test.go @@ -134,7 +134,7 @@ data "coderd_organization" "test" { ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/organization_resource.go b/internal/provider/organization_resource.go new file mode 100644 index 0000000..ad33d88 --- /dev/null +++ b/internal/provider/organization_resource.go @@ -0,0 +1,94 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/coder/terraform-provider-coderd/internal" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &OrganizationResource{} + +type OrganizationResource struct { + data *CoderdProviderData +} + +func NewOrganizationResource() resource.Resource { + return &OrganizationResource{} +} + +func (r *OrganizationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization" +} + +func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "An organization on the Coder deployment", + + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Username of the user.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 32), + stringvalidator.RegexMatches(nameValidRegex, "Username must be alphanumeric with hyphens."), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Display name of the user. Defaults to username.", + Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + }, + }, + + "id": schema.StringAttribute{ + CustomType: internal.UUIDType, + Computed: true, + MarkdownDescription: "Organization ID", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(*CoderdProviderData) + + if !ok { + resp.Diagnostics.AddError( + "Unable to configure provider data", + fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.data = data +} + +func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} +func (r *OrganizationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +} +func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +} +func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} +func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index bfeea5e..a5a81cf 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" ) // Ensure CoderdProvider satisfies various provider interfaces. @@ -42,7 +43,7 @@ type CoderdProviderModel struct { URL types.String `tfsdk:"url"` Token types.String `tfsdk:"token"` - DefaultOrganizationID UUID `tfsdk:"default_organization_id"` + DefaultOrganizationID internal.UUID `tfsdk:"default_organization_id"` } func (p *CoderdProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { @@ -78,7 +79,6 @@ This provider is only compatible with Coder version [2.10.1](https://github.com/ func (p *CoderdProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { var data CoderdProviderModel - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { diff --git a/internal/provider/template_data_source.go b/internal/provider/template_data_source.go index 3a5f59a..4d38547 100644 --- a/internal/provider/template_data_source.go +++ b/internal/provider/template_data_source.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -30,18 +31,18 @@ type TemplateDataSource struct { // TemplateDataSourceModel describes the data source data model. type TemplateDataSourceModel struct { // ((Organization and Name) or ID) must be set - OrganizationID UUID `tfsdk:"organization_id"` - ID UUID `tfsdk:"id"` - Name types.String `tfsdk:"name"` + OrganizationID internal.UUID `tfsdk:"organization_id"` + ID internal.UUID `tfsdk:"id"` + Name types.String `tfsdk:"name"` DisplayName types.String `tfsdk:"display_name"` // TODO: Provisioner - Description types.String `tfsdk:"description"` - ActiveVersionID UUID `tfsdk:"active_version_id"` - ActiveUserCount types.Int64 `tfsdk:"active_user_count"` - Deprecated types.Bool `tfsdk:"deprecated"` - DeprecationMessage types.String `tfsdk:"deprecation_message"` - Icon types.String `tfsdk:"icon"` + Description types.String `tfsdk:"description"` + ActiveVersionID internal.UUID `tfsdk:"active_version_id"` + ActiveUserCount types.Int64 `tfsdk:"active_user_count"` + Deprecated types.Bool `tfsdk:"deprecated"` + DeprecationMessage types.String `tfsdk:"deprecation_message"` + Icon types.String `tfsdk:"icon"` DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"` ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"` @@ -59,9 +60,9 @@ type TemplateDataSourceModel struct { RequireActiveVersion types.Bool `tfsdk:"require_active_version"` MaxPortShareLevel types.String `tfsdk:"max_port_share_level"` - CreatedByUserID UUID `tfsdk:"created_by_user_id"` - CreatedAt types.Int64 `tfsdk:"created_at"` // Unix timestamp - UpdatedAt types.Int64 `tfsdk:"updated_at"` // Unix timestamp + CreatedByUserID internal.UUID `tfsdk:"created_by_user_id"` + CreatedAt types.Int64 `tfsdk:"created_at"` // Unix timestamp + UpdatedAt types.Int64 `tfsdk:"updated_at"` // Unix timestamp ACL types.Object `tfsdk:"acl"` } @@ -77,13 +78,13 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe Attributes: map[string]schema.Attribute{ "organization_id": schema.StringAttribute{ MarkdownDescription: "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.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Optional: true, Computed: true, }, "id": schema.StringAttribute{ MarkdownDescription: "The ID of the template to retrieve. This field will be populated if a template name is supplied.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Optional: true, Computed: true, Validators: []validator.String{ @@ -108,7 +109,7 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe }, "active_version_id": schema.StringAttribute{ MarkdownDescription: "ID of the active version of the template.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, }, "active_user_count": schema.Int64Attribute{ @@ -190,7 +191,7 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe }, "created_by_user_id": schema.StringAttribute{ MarkdownDescription: "ID of the user who created the template.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, }, "created_at": schema.Int64Attribute{ @@ -253,7 +254,7 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques template, err = client.Template(ctx, data.ID.ValueUUID()) } else { if data.OrganizationID.ValueUUID() == uuid.Nil { - data.OrganizationID = UUIDValue(d.data.DefaultOrganizationID) + data.OrganizationID = internal.UUIDValue(d.data.DefaultOrganizationID) } if data.OrganizationID.ValueUUID() == uuid.Nil { resp.Diagnostics.AddError("Client Error", "name requires organization_id to be set") @@ -262,7 +263,7 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques template, err = client.TemplateByName(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString()) } if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", "Template not found. Marking as deleted.") resp.State.RemoveResource(ctx) return @@ -310,12 +311,12 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques data.ACL = aclObj data.AutostartPermittedDaysOfWeek = types.SetValueMust(types.StringType, autoStartDays) data.AutostopRequirement = asrObj - data.OrganizationID = UUIDValue(template.OrganizationID) - data.ID = UUIDValue(template.ID) + data.OrganizationID = internal.UUIDValue(template.OrganizationID) + data.ID = internal.UUIDValue(template.ID) data.Name = types.StringValue(template.Name) data.DisplayName = types.StringValue(template.DisplayName) data.Description = types.StringValue(template.Description) - data.ActiveVersionID = UUIDValue(template.ActiveVersionID) + data.ActiveVersionID = internal.UUIDValue(template.ActiveVersionID) data.ActiveUserCount = types.Int64Value(int64(template.ActiveUserCount)) data.Deprecated = types.BoolValue(template.Deprecated) data.DeprecationMessage = types.StringValue(template.DeprecationMessage) @@ -330,7 +331,7 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques data.TimeTilDormantAutoDeleteMillis = types.Int64Value(template.TimeTilDormantAutoDeleteMillis) data.RequireActiveVersion = types.BoolValue(template.RequireActiveVersion) data.MaxPortShareLevel = types.StringValue(string(template.MaxPortShareLevel)) - data.CreatedByUserID = UUIDValue(template.CreatedByID) + data.CreatedByUserID = internal.UUIDValue(template.CreatedByID) data.CreatedAt = types.Int64Value(template.CreatedAt.Unix()) data.UpdatedAt = types.Int64Value(template.UpdatedAt.Unix()) diff --git a/internal/provider/template_data_source_test.go b/internal/provider/template_data_source_test.go index f6759b3..b81cab3 100644 --- a/internal/provider/template_data_source_test.go +++ b/internal/provider/template_data_source_test.go @@ -251,7 +251,7 @@ data "coderd_template" "test" { }` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index a5834db..b25b02a 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" @@ -52,26 +53,26 @@ type TemplateResource struct { // TemplateResourceModel describes the resource data model. type TemplateResourceModel struct { - ID UUID `tfsdk:"id"` - - Name types.String `tfsdk:"name"` - DisplayName types.String `tfsdk:"display_name"` - Description types.String `tfsdk:"description"` - OrganizationID UUID `tfsdk:"organization_id"` - Icon types.String `tfsdk:"icon"` - DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"` - ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"` - AutostopRequirement types.Object `tfsdk:"auto_stop_requirement"` - AutostartPermittedDaysOfWeek types.Set `tfsdk:"auto_start_permitted_days_of_week"` - AllowUserCancelWorkspaceJobs types.Bool `tfsdk:"allow_user_cancel_workspace_jobs"` - AllowUserAutostart types.Bool `tfsdk:"allow_user_auto_start"` - AllowUserAutostop types.Bool `tfsdk:"allow_user_auto_stop"` - FailureTTLMillis types.Int64 `tfsdk:"failure_ttl_ms"` - TimeTilDormantMillis types.Int64 `tfsdk:"time_til_dormant_ms"` - TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"` - RequireActiveVersion types.Bool `tfsdk:"require_active_version"` - DeprecationMessage types.String `tfsdk:"deprecation_message"` - MaxPortShareLevel types.String `tfsdk:"max_port_share_level"` + ID internal.UUID `tfsdk:"id"` + + Name types.String `tfsdk:"name"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + OrganizationID internal.UUID `tfsdk:"organization_id"` + Icon types.String `tfsdk:"icon"` + DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"` + ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"` + AutostopRequirement types.Object `tfsdk:"auto_stop_requirement"` + AutostartPermittedDaysOfWeek types.Set `tfsdk:"auto_start_permitted_days_of_week"` + AllowUserCancelWorkspaceJobs types.Bool `tfsdk:"allow_user_cancel_workspace_jobs"` + AllowUserAutostart types.Bool `tfsdk:"allow_user_auto_start"` + AllowUserAutostop types.Bool `tfsdk:"allow_user_auto_stop"` + FailureTTLMillis types.Int64 `tfsdk:"failure_ttl_ms"` + TimeTilDormantMillis types.Int64 `tfsdk:"time_til_dormant_ms"` + TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"` + RequireActiveVersion types.Bool `tfsdk:"require_active_version"` + DeprecationMessage types.String `tfsdk:"deprecation_message"` + MaxPortShareLevel types.String `tfsdk:"max_port_share_level"` // If null, we are not managing ACL via Terraform (such as for AGPL). ACL types.Object `tfsdk:"acl"` @@ -150,19 +151,19 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features } type TemplateVersion struct { - ID UUID `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Message types.String `tfsdk:"message"` - Directory types.String `tfsdk:"directory"` - DirectoryHash types.String `tfsdk:"directory_hash"` - Active types.Bool `tfsdk:"active"` - TerraformVariables []Variable `tfsdk:"tf_vars"` - ProvisionerTags []Variable `tfsdk:"provisioner_tags"` + ID internal.UUID `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Message types.String `tfsdk:"message"` + Directory types.String `tfsdk:"directory"` + DirectoryHash types.String `tfsdk:"directory_hash"` + Active types.Bool `tfsdk:"active"` + TerraformVariables []Variable `tfsdk:"tf_vars"` + ProvisionerTags []Variable `tfsdk:"provisioner_tags"` } type Versions []TemplateVersion -func (v Versions) ByID(id UUID) *TemplateVersion { +func (v Versions) ByID(id internal.UUID) *TemplateVersion { for _, m := range v { if m.ID.Equal(id) { return &m @@ -249,7 +250,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "The ID of the template.", - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), @@ -278,7 +279,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques }, "organization_id": schema.StringAttribute{ MarkdownDescription: "The ID of the organization. Defaults to the provider's default organization", - CustomType: UUIDType, + CustomType: internal.UUIDType, Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ @@ -409,7 +410,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, }, "name": schema.StringAttribute{ @@ -489,7 +490,7 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques } if data.OrganizationID.IsUnknown() { - data.OrganizationID = UUIDValue(r.data.DefaultOrganizationID) + data.OrganizationID = internal.UUIDValue(r.data.DefaultOrganizationID) } if data.DisplayName.IsUnknown() { @@ -563,10 +564,10 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques return } } - data.Versions[idx].ID = UUIDValue(versionResp.ID) + data.Versions[idx].ID = internal.UUIDValue(versionResp.ID) data.Versions[idx].Name = types.StringValue(versionResp.Name) } - data.ID = UUIDValue(templateResp.ID) + data.ID = internal.UUIDValue(templateResp.ID) data.DisplayName = types.StringValue(templateResp.DisplayName) // TODO: Remove this update call once this provider requires a Coder @@ -610,7 +611,7 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r template, err := client.Template(ctx, templateID) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Template with ID %s not found. Marking as deleted.", templateID.String())) resp.State.RemoveResource(ctx) return @@ -681,7 +682,7 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques } if newState.OrganizationID.IsUnknown() { - newState.OrganizationID = UUIDValue(r.data.DefaultOrganizationID) + newState.OrganizationID = internal.UUIDValue(r.data.DefaultOrganizationID) } if newState.DisplayName.IsUnknown() { @@ -760,7 +761,7 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template version: %s", err)) return } - newState.Versions[idx].ID = UUIDValue(versionResp.ID) + newState.Versions[idx].ID = internal.UUIDValue(versionResp.ID) newState.Versions[idx].Name = types.StringValue(versionResp.Name) if newState.Versions[idx].Active.ValueBool() { err := markActive(ctx, client, templateID, newState.Versions[idx].ID.ValueUUID()) @@ -957,7 +958,7 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif } for i := range planVersions { - hash, err := computeDirectoryHash(planVersions[i].Directory.ValueString()) + hash, err := internal.DirectoryHash(planVersions[i].Directory.ValueString()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to compute directory hash: %s", err)) return @@ -1063,7 +1064,7 @@ func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequ var logs []codersdk.ProvisionerJobLog directory := req.Version.Directory.ValueString() tflog.Info(ctx, "uploading directory") - uploadResp, err := uploadDirectory(ctx, client, slog.Make(newTFLogSink(ctx)), directory) + uploadResp, err := uploadDirectory(ctx, client, slog.Make(internal.NewLogSink(ctx)), directory) if err != nil { return nil, fmt.Errorf("failed to upload directory: %s", err), logs } @@ -1183,7 +1184,7 @@ func (r *TemplateResourceModel) readResponse(ctx context.Context, template *code r.Name = types.StringValue(template.Name) r.DisplayName = types.StringValue(template.DisplayName) r.Description = types.StringValue(template.Description) - r.OrganizationID = UUIDValue(template.OrganizationID) + r.OrganizationID = internal.UUIDValue(template.OrganizationID) r.Icon = types.StringValue(template.Icon) r.DefaultTTLMillis = types.Int64Value(template.DefaultTTLMillis) r.ActivityBumpMillis = types.Int64Value(template.ActivityBumpMillis) @@ -1364,7 +1365,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe // Versions whose Terraform configuration has not changed will have known // IDs at this point, so we need to set this manually. if !ok { - planVersions[i].ID = NewUUIDUnknown() + planVersions[i].ID = internal.NewUUIDUnknown() // We might have the old randomly generated name in the plan, // so unless the user has set it to a new one, we need to set it to // unknown so that a new one is generated @@ -1377,7 +1378,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe // If the name is the same, use the existing ID, and remove // it from the previous version candidates if planVersions[i].Name.ValueString() == prev.Name { - planVersions[i].ID = UUIDValue(prev.ID) + planVersions[i].ID = internal.UUIDValue(prev.ID) lv[planVersions[i].DirectoryHash.ValueString()] = append(prevList[:j], prevList[j+1:]...) break } @@ -1390,7 +1391,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe for i := range planVersions { prevList := lv[planVersions[i].DirectoryHash.ValueString()] if len(prevList) > 0 && planVersions[i].ID.IsUnknown() { - planVersions[i].ID = UUIDValue(prevList[0].ID) + planVersions[i].ID = internal.UUIDValue(prevList[0].ID) if planVersions[i].Name.IsUnknown() { planVersions[i].Name = types.StringValue(prevList[0].Name) } @@ -1407,7 +1408,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe continue } if tfVariablesChanged(prevs, &planVersions[i]) { - planVersions[i].ID = NewUUIDUnknown() + planVersions[i].ID = internal.NewUUIDUnknown() // We could always set the name to unknown here, to generate a // random one (this is what the Web UI currently does when // only updating tfvars). diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go index b9d7ae3..7b2235a 100644 --- a/internal/provider/template_resource_test.go +++ b/internal/provider/template_resource_test.go @@ -764,7 +764,7 @@ func (c testAccTemplateACLConfig) String(t *testing.T) string { ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} @@ -794,7 +794,7 @@ func (c testAccAutostopRequirementConfig) String(t *testing.T) string { } ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} @@ -859,7 +859,7 @@ resource "coderd_template" "test" { ` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/user_data_source.go b/internal/provider/user_data_source.go index be367ea..cc493ca 100644 --- a/internal/provider/user_data_source.go +++ b/internal/provider/user_data_source.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -29,8 +30,8 @@ type UserDataSource struct { // UserDataSourceModel describes the data source data model. type UserDataSourceModel struct { // Username or ID must be set - ID UUID `tfsdk:"id"` - Username types.String `tfsdk:"username"` + ID internal.UUID `tfsdk:"id"` + Username types.String `tfsdk:"username"` Name types.String `tfsdk:"name"` Email types.String `tfsdk:"email"` @@ -55,7 +56,7 @@ func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaReques // Validation handled by ConfigValidators Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - CustomType: UUIDType, + CustomType: internal.UUIDType, MarkdownDescription: "The ID of the user to retrieve. This field will be populated if a username is supplied.", Optional: true, }, @@ -91,7 +92,7 @@ func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaReques "organization_ids": schema.SetAttribute{ MarkdownDescription: "IDs of organizations the user is associated with.", Computed: true, - ElementType: UUIDType, + ElementType: internal.UUIDType, }, "created_at": schema.Int64Attribute{ MarkdownDescription: "Unix timestamp of when the user was created.", @@ -149,7 +150,7 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } user, err := client.User(ctx, ident) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with identifier %q not found. Marking as deleted.", ident)) resp.State.RemoveResource(ctx) return @@ -169,7 +170,7 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } - data.ID = UUIDValue(user.ID) + data.ID = internal.UUIDValue(user.ID) data.Username = types.StringValue(user.Username) data.Name = types.StringValue(user.Name) data.Email = types.StringValue(user.Email) @@ -183,9 +184,9 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r orgIDs := make([]attr.Value, 0, len(user.OrganizationIDs)) for _, orgID := range user.OrganizationIDs { - orgIDs = append(orgIDs, UUIDValue(orgID)) + orgIDs = append(orgIDs, internal.UUIDValue(orgID)) } - data.OrganizationIDs = types.SetValueMust(UUIDType, orgIDs) + data.OrganizationIDs = types.SetValueMust(internal.UUIDType, orgIDs) data.CreatedAt = types.Int64Value(user.CreatedAt.Unix()) data.LastSeenAt = types.Int64Value(user.LastSeenAt.Unix()) data.ThemePreference = types.StringValue(user.ThemePreference) diff --git a/internal/provider/user_data_source_test.go b/internal/provider/user_data_source_test.go index ebdab83..46fb9cc 100644 --- a/internal/provider/user_data_source_test.go +++ b/internal/provider/user_data_source_test.go @@ -128,7 +128,7 @@ data "coderd_user" "test" { }` funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go index 3fa570e..8970569 100644 --- a/internal/provider/user_resource.go +++ b/internal/provider/user_resource.go @@ -5,6 +5,9 @@ import ( "fmt" "strings" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal" + "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -20,9 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" - - "github.com/coder/coder/v2/codersdk" - "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -40,7 +40,7 @@ type UserResource struct { // UserResourceModel describes the resource data model. type UserResourceModel struct { - ID UUID `tfsdk:"id"` + ID internal.UUID `tfsdk:"id"` Username types.String `tfsdk:"username"` Name types.String `tfsdk:"name"` @@ -61,7 +61,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - CustomType: UUIDType, + CustomType: internal.UUIDType, Computed: true, MarkdownDescription: "User ID", PlanModifiers: []planmodifier.String{ @@ -191,7 +191,7 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r tflog.Info(ctx, "successfully created user", map[string]any{ "id": user.ID.String(), }) - data.ID = UUIDValue(user.ID) + data.ID = internal.UUIDValue(user.ID) tflog.Info(ctx, "updating user profile") name := data.Username @@ -250,7 +250,7 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp user, err := client.User(ctx, data.ID.ValueString()) if err != nil { - if isNotFound(err) { + if internal.IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with ID %q not found. Marking as deleted.", data.ID.ValueString())) resp.State.RemoveResource(ctx) return diff --git a/internal/provider/user_resource_test.go b/internal/provider/user_resource_test.go index b9c6bb7..a605d97 100644 --- a/internal/provider/user_resource_test.go +++ b/internal/provider/user_resource_test.go @@ -137,7 +137,7 @@ resource "coderd_user" "test" { ` // Define template functions funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/workspace_proxy_resource.go b/internal/provider/workspace_proxy_resource.go index 211c778..9575227 100644 --- a/internal/provider/workspace_proxy_resource.go +++ b/internal/provider/workspace_proxy_resource.go @@ -142,7 +142,7 @@ func (r *WorkspaceProxyResource) Read(ctx context.Context, req resource.ReadRequ client := r.data.Client wsp, err := client.WorkspaceProxyByID(ctx, data.ID.ValueUUID()) if err != nil { - if isNotFound(err) { + if IsNotFound(err) { resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Workspace proxy with ID %s not found. Marking as deleted.", data.ID.ValueString())) resp.State.RemoveResource(ctx) return diff --git a/internal/provider/workspace_proxy_resource_test.go b/internal/provider/workspace_proxy_resource_test.go index 88d7013..9684e26 100644 --- a/internal/provider/workspace_proxy_resource_test.go +++ b/internal/provider/workspace_proxy_resource_test.go @@ -109,7 +109,7 @@ resource "coderd_workspace_proxy" "test" { ` // Define template functions funcMap := template.FuncMap{ - "orNull": PrintOrNull, + "orNull": printOrNull, } buf := strings.Builder{} diff --git a/internal/provider/util.go b/internal/util.go similarity index 78% rename from internal/provider/util.go rename to internal/util.go index 720259c..1481a76 100644 --- a/internal/provider/util.go +++ b/internal/util.go @@ -1,4 +1,4 @@ -package provider +package internal import ( "crypto/sha256" @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "github.com/coder/coder/v2/codersdk" "github.com/google/uuid" @@ -57,35 +58,43 @@ func PrintOrNull(v any) string { } } -func computeDirectoryHash(directory string) (string, error) { - var files []string +func DirectoryHash(directory string) (string, error) { + hash := sha256.New() + count := 0 + + // filepath.Walk always proceeds in lexical order, so we don't need to worry + // about order variance from call to call producing different hash results. err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if !info.IsDir() { - files = append(files, path) + if info.IsDir() { + return nil + } + + count++ + data, err := os.ReadFile(path) + if err != nil { + return err } + hash.Write([]byte(path)) + hash.Write(data) + return nil }) + if err != nil { return "", err } - hash := sha256.New() - for _, file := range files { - data, err := os.ReadFile(file) - if err != nil { - return "", err - } - hash.Write(data) - } + hash.Write([]byte(strconv.Itoa(count))) + return hex.EncodeToString(hash.Sum(nil)), nil } -// memberDiff returns the members to add and remove from the group, given the current members and the planned members. +// MemberDiff returns the members to add and remove from the group, given the current members and the planned members. // plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set. -func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) { +func MemberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) { curSet := make(map[uuid.UUID]struct{}, len(curMembers)) planSet := make(map[uuid.UUID]struct{}, len(plannedMembers)) @@ -106,7 +115,7 @@ func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []st return add, remove } -func isNotFound(err error) bool { +func IsNotFound(err error) bool { var sdkErr *codersdk.Error return errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound } diff --git a/internal/provider/uuid.go b/internal/uuid.go similarity index 94% rename from internal/provider/uuid.go rename to internal/uuid.go index 8cd8912..13c7b5d 100644 --- a/internal/provider/uuid.go +++ b/internal/uuid.go @@ -1,4 +1,4 @@ -package provider +package internal import ( "context" @@ -25,7 +25,7 @@ func (t uuidType) String() string { return "UUID" } -func (t uuidType) ValueType(ctx context.Context) attr.Value { +func (t uuidType) ValueType(_ context.Context) attr.Value { return UUID{} } @@ -38,7 +38,7 @@ func (t uuidType) Equal(o attr.Type) bool { } // ValueFromString implements basetypes.StringTypable. -func (t uuidType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { +func (t uuidType) ValueFromString(_ context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { var diags diag.Diagnostics if in.IsNull() { diff --git a/internal/provider/uuid_internal_test.go b/internal/uuid_internal_test.go similarity index 99% rename from internal/provider/uuid_internal_test.go rename to internal/uuid_internal_test.go index 6283bb9..25073ef 100644 --- a/internal/provider/uuid_internal_test.go +++ b/internal/uuid_internal_test.go @@ -1,4 +1,4 @@ -package provider +package internal import ( "context" 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