From 620b2c921e4c72112d26c21e65aed0173ff26f83 Mon Sep 17 00:00:00 2001 From: David Debeau Date: Wed, 28 Aug 2024 10:06:24 -0500 Subject: [PATCH 1/3] Create OrganizationTeamDataSource --- code_generator/provider_code_spec.json | 45 ++++++ .../organization_team_data_source_gen.go | 51 +++++++ .../provider/organization_team_data_source.go | 137 ++++++++++++++++++ .../organization_team_data_source_test.go | 60 ++++++++ internal/provider/provider.go | 1 + 5 files changed, 294 insertions(+) create mode 100644 internal/datasource_organization_team/organization_team_data_source_gen.go create mode 100644 internal/provider/organization_team_data_source.go create mode 100644 internal/provider/organization_team_data_source_test.go diff --git a/code_generator/provider_code_spec.json b/code_generator/provider_code_spec.json index f71f652..7ec6b77 100644 --- a/code_generator/provider_code_spec.json +++ b/code_generator/provider_code_spec.json @@ -365,6 +365,51 @@ ] } }, + { + "name": "organization_team", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed", + "description": "Markdown description" + } + }, + { + "name": "members", + "list": { + "computed_optional_required": "computed", + "description": "List of team members", + "element_type": { + "string": {} + } + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Team name" + } + }, + { + "name": "orgname", + "string": { + "computed_optional_required": "required", + "description": "Organization name" + } + }, + { + "name": "role", + "string": { + "computed_optional_required": "computed", + "description": "Team role" + } + } + ] + } + }, { "name": "organization_team_permission", "schema": { diff --git a/internal/datasource_organization_team/organization_team_data_source_gen.go b/internal/datasource_organization_team/organization_team_data_source_gen.go new file mode 100644 index 0000000..aee0544 --- /dev/null +++ b/internal/datasource_organization_team/organization_team_data_source_gen.go @@ -0,0 +1,51 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_organization_team + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func OrganizationTeamDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Computed: true, + Description: "Markdown description", + MarkdownDescription: "Markdown description", + }, + "members": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "List of team members", + MarkdownDescription: "List of team members", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Team name", + MarkdownDescription: "Team name", + }, + "orgname": schema.StringAttribute{ + Required: true, + Description: "Organization name", + MarkdownDescription: "Organization name", + }, + "role": schema.StringAttribute{ + Computed: true, + Description: "Team role", + MarkdownDescription: "Team role", + }, + }, + } +} + +type OrganizationTeamModel struct { + Description types.String `tfsdk:"description"` + Members types.List `tfsdk:"members"` + Name types.String `tfsdk:"name"` + Orgname types.String `tfsdk:"orgname"` + Role types.String `tfsdk:"role"` +} diff --git a/internal/provider/organization_team_data_source.go b/internal/provider/organization_team_data_source.go new file mode 100644 index 0000000..c62cbad --- /dev/null +++ b/internal/provider/organization_team_data_source.go @@ -0,0 +1,137 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/enthought/terraform-provider-quay/quay_api" + "terraform-provider-quay/internal/datasource_organization_team" +) + +var ( + _ datasource.DataSource = (*organizationTeamDataSource)(nil) + _ datasource.DataSourceWithConfigure = (*organizationTeamDataSource)(nil) +) + +func NewOrganizationTeamDataSource() datasource.DataSource { + return &organizationTeamDataSource{} +} + +type organizationTeamDataSource struct { + client *quay_api.APIClient +} + +func (d *organizationTeamDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization_team" +} + +func (d *organizationTeamDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_organization_team.OrganizationTeamDataSourceSchema(ctx) +} + +func (d *organizationTeamDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data datasource_organization_team.OrganizationTeamModel + var resTeamData teamModelJSON + var resOrgData organizationModelJSON + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + teamName := data.Name.ValueString() + orgName := data.Orgname.ValueString() + + // Get members + httpRes, err := d.client.TeamAPI.GetOrganizationTeamMembers(context.Background(), orgName, teamName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay team", "Could not read Quay team, unexpected error: "+errDetail) + return + } + + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resTeamData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set members + var memList []string + for _, member := range resTeamData.Members { + memList = append(memList, member.Name) + } + members, diags := types.ListValueFrom(ctx, types.StringType, memList) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + data.Members = members + + // Get org data + httpRes, err = d.client.OrganizationAPI.GetOrganization(context.Background(), orgName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay team", "Could not read Quay team, unexpected error: "+errDetail) + return + } + + body, err = io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resOrgData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set Role + data.Role = types.StringValue(resOrgData.Teams[teamName].Role) + + // Set Description + data.Description = types.StringValue(resOrgData.Teams[teamName].Description) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *organizationTeamDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*quay_api.APIClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *quay_api.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + + d.client = client +} diff --git a/internal/provider/organization_team_data_source_test.go b/internal/provider/organization_team_data_source_test.go new file mode 100644 index 0000000..1b31478 --- /dev/null +++ b/internal/provider/organization_team_data_source_test.go @@ -0,0 +1,60 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationTeamDataSource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: providerConfig + ` +resource "quay_organization" "org_team_data" { + name = "org_team_data" + email = "quay+org_team_data@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.org_team_data.name +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team_data.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_data.name + role = "member" + description = "test" + members = [ + quay_organization_robot.test.fullname + ] +} + +data "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_data.name + + depends_on = [ + quay_organization_team.test + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("data.quay_organization_team.test", "orgname", "org_team_data"), + resource.TestCheckResourceAttr("data.quay_organization_team.test", "role", "member"), + resource.TestCheckResourceAttr("data.quay_organization_team.test", "description", "test"), + resource.TestCheckResourceAttr("data.quay_organization_team.test", "members.0", "org_team_data+test"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d620476..fd3a35c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -153,6 +153,7 @@ func (p *quayProvider) DataSources(_ context.Context) []func() datasource.DataSo NewOrganizationDataSource, NewRepositoryDataSource, NewOrganizationTeamPermissionDataSource, + NewOrganizationTeamDataSource, } } From 5a0bef11c6523fca256c93a27331d282aeda6696 Mon Sep 17 00:00:00 2001 From: David Debeau Date: Wed, 28 Aug 2024 10:38:38 -0500 Subject: [PATCH 2/3] Create OrganizationRobotDataSource --- code_generator/provider_code_spec.json | 35 ++++++ .../organization_robot_data_source_gen.go | 44 ++++++++ .../organization_robot_data_source.go | 100 ++++++++++++++++++ .../organization_robot_data_source_test.go | 44 ++++++++ internal/provider/provider.go | 1 + 5 files changed, 224 insertions(+) create mode 100644 internal/datasource_organization_robot/organization_robot_data_source_gen.go create mode 100644 internal/provider/organization_robot_data_source.go create mode 100644 internal/provider/organization_robot_data_source_test.go diff --git a/code_generator/provider_code_spec.json b/code_generator/provider_code_spec.json index 7ec6b77..3eec48e 100644 --- a/code_generator/provider_code_spec.json +++ b/code_generator/provider_code_spec.json @@ -365,6 +365,41 @@ ] } }, + { + "name": "organization_robot", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed", + "description": "Text description" + } + }, + { + "name": "fullname", + "string": { + "computed_optional_required": "computed", + "description": "Robot full name" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Robot short name" + } + }, + { + "name": "orgname", + "string": { + "computed_optional_required": "required", + "description": "Organization name" + } + } + ] + } + }, { "name": "organization_team", "schema": { diff --git a/internal/datasource_organization_robot/organization_robot_data_source_gen.go b/internal/datasource_organization_robot/organization_robot_data_source_gen.go new file mode 100644 index 0000000..6b8a941 --- /dev/null +++ b/internal/datasource_organization_robot/organization_robot_data_source_gen.go @@ -0,0 +1,44 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_organization_robot + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func OrganizationRobotDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Computed: true, + Description: "Text description", + MarkdownDescription: "Text description", + }, + "fullname": schema.StringAttribute{ + Computed: true, + Description: "Robot full name", + MarkdownDescription: "Robot full name", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Robot short name", + MarkdownDescription: "Robot short name", + }, + "orgname": schema.StringAttribute{ + Required: true, + Description: "Organization name", + MarkdownDescription: "Organization name", + }, + }, + } +} + +type OrganizationRobotModel struct { + Description types.String `tfsdk:"description"` + Fullname types.String `tfsdk:"fullname"` + Name types.String `tfsdk:"name"` + Orgname types.String `tfsdk:"orgname"` +} diff --git a/internal/provider/organization_robot_data_source.go b/internal/provider/organization_robot_data_source.go new file mode 100644 index 0000000..7e41b3e --- /dev/null +++ b/internal/provider/organization_robot_data_source.go @@ -0,0 +1,100 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/enthought/terraform-provider-quay/quay_api" + "terraform-provider-quay/internal/datasource_organization_robot" +) + +var ( + _ datasource.DataSource = (*organizationRobotDataSource)(nil) + _ datasource.DataSourceWithConfigure = (*organizationRobotDataSource)(nil) +) + +func NewOrganizationRobotDataSource() datasource.DataSource { + return &organizationRobotDataSource{} +} + +type organizationRobotDataSource struct { + client *quay_api.APIClient +} + +func (d *organizationRobotDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization_robot" +} + +func (d *organizationRobotDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_organization_robot.OrganizationRobotDataSourceSchema(ctx) +} + +func (d *organizationRobotDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data datasource_organization_robot.OrganizationRobotModel + var resRobotData organizationRobotModelJSON + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + orgName := data.Orgname.ValueString() + robotName := data.Name.ValueString() + + // Get robot + httpRes, err := d.client.RobotAPI.GetOrgRobot(context.Background(), orgName, robotName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError( + "Error reading Quay org robot", + "Could not read Quay org robot, unexpected error: "+errDetail) + return + } + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resRobotData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set Description + data.Description = types.StringValue(resRobotData.Description) + + // Set robot full name + data.Fullname = types.StringValue(orgName + "+" + robotName) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *organizationRobotDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*quay_api.APIClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *quay_api.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + + d.client = client +} diff --git a/internal/provider/organization_robot_data_source_test.go b/internal/provider/organization_robot_data_source_test.go new file mode 100644 index 0000000..bef1fb5 --- /dev/null +++ b/internal/provider/organization_robot_data_source_test.go @@ -0,0 +1,44 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationRobotDataSource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` +resource "quay_organization" "org_robot_data" { + name = "org_robot_data" + email = "quay+org_robot_data@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_robot_data.name + description = "test" +} + +data "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_robot_data.name + + depends_on = [ + quay_organization_robot.test + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_robot.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "orgname", "org_robot_data"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "description", "test"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "fullname", "org_robot_data+test"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index fd3a35c..7b025ae 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -154,6 +154,7 @@ func (p *quayProvider) DataSources(_ context.Context) []func() datasource.DataSo NewRepositoryDataSource, NewOrganizationTeamPermissionDataSource, NewOrganizationTeamDataSource, + NewOrganizationRobotDataSource, } } From 649188cfac434cc9033a8fb5bba1496412bac23b Mon Sep 17 00:00:00 2001 From: David Debeau Date: Wed, 28 Aug 2024 10:44:54 -0500 Subject: [PATCH 3/3] Update documentation and examples --- docs/data-sources/organization_robot.md | 38 ++++++++++++++ docs/data-sources/organization_team.md | 49 +++++++++++++++++++ .../quay_organization_robot/data-source.tf | 9 ++++ .../quay_organization_team/data-source.tf | 19 +++++++ 4 files changed, 115 insertions(+) create mode 100644 docs/data-sources/organization_robot.md create mode 100644 docs/data-sources/organization_team.md create mode 100644 examples/data-sources/quay_organization_robot/data-source.tf create mode 100644 examples/data-sources/quay_organization_team/data-source.tf diff --git a/docs/data-sources/organization_robot.md b/docs/data-sources/organization_robot.md new file mode 100644 index 0000000..b613d92 --- /dev/null +++ b/docs/data-sources/organization_robot.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_organization_robot Data Source - quay" +subcategory: "" +description: |- + +--- + +# quay_organization_robot (Data Source) + + + +## Example Usage + +```terraform +resource "quay_organization" "org" { + name = "org" + email = "quay+org@example.com" +} + +data "quay_organization_robot" "robot" { + name = "robot" + orgname = quay_organization.org.name +} +``` + + +## Schema + +### Required + +- `name` (String) Robot short name +- `orgname` (String) Organization name + +### Read-Only + +- `description` (String) Text description +- `fullname` (String) Robot full name diff --git a/docs/data-sources/organization_team.md b/docs/data-sources/organization_team.md new file mode 100644 index 0000000..bd9955f --- /dev/null +++ b/docs/data-sources/organization_team.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_organization_team Data Source - quay" +subcategory: "" +description: |- + +--- + +# quay_organization_team (Data Source) + + + +## Example Usage + +```terraform +resource "quay_organization" "org" { + name = "org" + email = "quay+org@example.com" +} + +resource "quay_repository" "repo" { + name = "repo" + namespace = quay_organization.org.name +} + +resource "quay_organization_robot" "robot" { + name = "robot" + orgname = quay_organization.org.name +} + +data "quay_organization_team" "team" { + name = "team" + orgname = quay_organization.org.name +} +``` + + +## Schema + +### Required + +- `name` (String) Team name +- `orgname` (String) Organization name + +### Read-Only + +- `description` (String) Markdown description +- `members` (List of String) List of team members +- `role` (String) Team role diff --git a/examples/data-sources/quay_organization_robot/data-source.tf b/examples/data-sources/quay_organization_robot/data-source.tf new file mode 100644 index 0000000..3c45431 --- /dev/null +++ b/examples/data-sources/quay_organization_robot/data-source.tf @@ -0,0 +1,9 @@ +resource "quay_organization" "org" { + name = "org" + email = "quay+org@example.com" +} + +data "quay_organization_robot" "robot" { + name = "robot" + orgname = quay_organization.org.name +} diff --git a/examples/data-sources/quay_organization_team/data-source.tf b/examples/data-sources/quay_organization_team/data-source.tf new file mode 100644 index 0000000..4cc44b1 --- /dev/null +++ b/examples/data-sources/quay_organization_team/data-source.tf @@ -0,0 +1,19 @@ +resource "quay_organization" "org" { + name = "org" + email = "quay+org@example.com" +} + +resource "quay_repository" "repo" { + name = "repo" + namespace = quay_organization.org.name +} + +resource "quay_organization_robot" "robot" { + name = "robot" + orgname = quay_organization.org.name +} + +data "quay_organization_team" "team" { + name = "team" + orgname = quay_organization.org.name +}