Skip to content

Commit

Permalink
fix resource org team member association and write tests and validations
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhristovski committed Aug 29, 2024
1 parent 18f0d23 commit 89a0d68
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 264 deletions.
2 changes: 1 addition & 1 deletion examples/load-test/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ resource "docker_org_team" "terraform_team" {
}

# Team associations with variations
resource "docker_org_team_member_association" "example_association" {
resource "docker_org_team_member" "example_association" {
count = 200
org_name = "dockerterraform"
team_name = docker_org_team.terraform_team[count.index].team_name
Expand Down
4 changes: 2 additions & 2 deletions examples/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ resource "docker_org_team" "terraform-team" {
}

# Team association
resource "docker_org_team_member_association" "example_association" {
resource "docker_org_team_member" "example_association" {
org_name = "dockerterraform"
team_name = resource.docker_org_team.terraform-team.team_name
user_names = ["forrestloomis371", "username-placeholder"]
Expand Down Expand Up @@ -60,7 +60,7 @@ output "org_team_output" {
}

output "org_team_association_output" {
value = resource.docker_org_team_member_association.example_association
value = resource.docker_org_team_member.example_association
}

# output "access_tokens_uuids_output" {
Expand Down
7 changes: 0 additions & 7 deletions internal/hubclient/client_organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,6 @@ func (c *Client) ListOrgTeamMembers(ctx context.Context, orgName string, teamNam
return membersResponse, err
}

// // TODO: This is returning a 503 for some reason... moving on as to no stay blocked
// func (c *Client) ListOrgTeamMembers(ctx context.Context, orgName string, teamName string) ([]string, error) {
// var members []string
// err := c.sendRequest(ctx, "GET", fmt.Sprintf("/orgs/%s/teams/%s/members", orgName, teamName), nil, &members)
// return members, err
// }

func (c *Client) GetOrgSettingImageAccessManagement(ctx context.Context, orgName string) (OrgSettingImageAccessManagement, error) {
var settings OrgSettingImageAccessManagement
err := c.sendRequest(ctx, "GET", fmt.Sprintf("/orgs/%s/settings/", orgName), nil, &settings)
Expand Down
6 changes: 6 additions & 0 deletions internal/provider/data_source_org_team_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package provider
import (
"context"
"fmt"
"regexp"

"github.com/docker/terraform-provider-docker/internal/hubclient"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

Expand Down Expand Up @@ -68,6 +71,9 @@ func (d *OrgTeamMemberDataSource) Schema(ctx context.Context, req datasource.Sch
"team_name": schema.StringAttribute{
MarkdownDescription: "Team name",
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9_-]{3,30}$`), "Team name must be 3-30 characters long and can only contain letters, numbers, underscores, or hyphens."),
},
},
"members": schema.ListNestedAttribute{
MarkdownDescription: "List of members",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (p *DockerProvider) Resources(ctx context.Context) []func() resource.Resour
NewOrgSettingImageAccessManagementResource,
NewOrgSettingRegistryAccessManagementResource,
NewOrgTeamResource,
NewOrgTeamMemberAssociationResource,
NewOrgTeamMemberResource,
NewRepositoryResource,
NewRepositoryTeamPermissionResource,
NewOrgMemberResource,
Expand Down
4 changes: 4 additions & 0 deletions internal/provider/resource_org_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"regexp"

"github.com/docker/terraform-provider-docker/internal/hubclient"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
Expand Down Expand Up @@ -79,6 +80,9 @@ func (r *OrgMemberResource) Schema(ctx context.Context, req resource.SchemaReque
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9_-]{3,30}$`), "Team name must be 3-30 characters long and can only contain letters, numbers, underscores, or hyphens."),
},
},
"user_name": schema.StringAttribute{
MarkdownDescription: "User name (email) of the member being associated with the team",
Expand Down
7 changes: 6 additions & 1 deletion internal/provider/resource_org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package provider
import (
"context"
"fmt"
"regexp"
"strings"

"github.com/docker/terraform-provider-docker/internal/hubclient"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"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"
"github.com/hashicorp/terraform-plugin-framework/types"
)

Expand Down Expand Up @@ -65,7 +68,6 @@ func (r *OrgTeamResource) Schema(ctx context.Context, req resource.SchemaRequest
~> **Note** Only available when authenticated with a username and password as an owner of the org.
`,

Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
MarkdownDescription: "The numeric id associated to the team",
Expand All @@ -84,6 +86,9 @@ func (r *OrgTeamResource) Schema(ctx context.Context, req resource.SchemaRequest
"team_name": schema.StringAttribute{
MarkdownDescription: "Team name",
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9_-]{3,30}$`), "Team name must be 3-30 characters long and can only contain letters, numbers, underscores, or hyphens."),
},
},
"team_description": schema.StringAttribute{
MarkdownDescription: "Team description",
Expand Down
196 changes: 196 additions & 0 deletions internal/provider/resource_org_team_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package provider

import (
"context"
"fmt"
"regexp"
"strings"

"github.com/docker/terraform-provider-docker/internal/hubclient"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"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"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var (
_ resource.Resource = &OrgTeamMemberResource{}
_ resource.ResourceWithConfigure = &OrgTeamMemberResource{}
_ resource.ResourceWithImportState = &OrgTeamMemberResource{}
)

func NewOrgTeamMemberResource() resource.Resource {
return &OrgTeamMemberResource{}
}

type OrgTeamMemberResource struct {
client *hubclient.Client
}

type OrgTeamMemberResourceModel struct {
ID types.String `tfsdk:"id"`
OrgName types.String `tfsdk:"org_name"`
TeamName types.String `tfsdk:"team_name"`
UserName types.String `tfsdk:"user_name"`
}

func (r *OrgTeamMemberResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*hubclient.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *hubclient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client
}

func (r *OrgTeamMemberResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_org_team_member"
}

func (r *OrgTeamMemberResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: `Manages team members associated with an organization.
~> **Note** Only available when authenticated with a username and password as an owner of the org.
`,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: "The ID of the team member",
Computed: true,
},
"org_name": schema.StringAttribute{
MarkdownDescription: "Organization name",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"team_name": schema.StringAttribute{
MarkdownDescription: "Team name",
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9_-]{3,30}$`), "Team name must be 3-30 characters long and can only contain letters, numbers, underscores, or hyphens."),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"user_name": schema.StringAttribute{
MarkdownDescription: "User name to be added to the team",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func (r *OrgTeamMemberResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data OrgTeamMemberResourceModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

err := r.client.AddOrgTeamMember(ctx, data.OrgName.ValueString(), data.TeamName.ValueString(), data.UserName.ValueString())
if err != nil {
resp.Diagnostics.AddError("Unable to add team member", err.Error())
return
}

data.ID = types.StringValue(fmt.Sprintf("%s/%s/%s", data.OrgName.ValueString(), data.TeamName.ValueString(), data.UserName.ValueString()))

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *OrgTeamMemberResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data OrgTeamMemberResourceModel

// Read Terraform state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

// Call the new API to list members of the team
membersResponse, err := r.client.ListOrgTeamMembers(ctx, data.OrgName.ValueString(), data.TeamName.ValueString())
if err != nil {
resp.Diagnostics.AddError("Unable to read org_team_member resource", fmt.Sprintf("Error retrieving team members: %v", err))
return
}

// Check if the specified user is in the team
found := false
for _, member := range membersResponse.Results {
if member.Username == data.UserName.ValueString() {
found = true
break
}
}

if !found {
// If the user is not found in the team, remove the resource from state
resp.Diagnostics.AddWarning("User not found", fmt.Sprintf("User %s is not a member of team %s. Removing from state.", data.UserName.ValueString(), data.TeamName.ValueString()))
resp.State.RemoveResource(ctx)
return
}

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *OrgTeamMemberResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Since we added RequiresReplace() to user_name, there's no need to handle
// the update logic manually here
}

func (r *OrgTeamMemberResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data OrgTeamMemberResourceModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

err := r.client.DeleteOrgTeamMember(ctx, data.OrgName.ValueString(), data.TeamName.ValueString(), data.UserName.ValueString())
if err != nil {
resp.Diagnostics.AddError("Unable to delete team member", err.Error())
return
}

resp.State.RemoveResource(ctx)
}

func (r *OrgTeamMemberResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Import logic for the resource
idParts := strings.Split(req.ID, "/")

if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: org_name/team_name/user_name. Got: %q", req.ID),
)
return
}

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("org_name"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("team_name"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_name"), idParts[2])...)
}
Loading

0 comments on commit 89a0d68

Please sign in to comment.