diff --git a/code_generator/provider_code_spec.json b/code_generator/provider_code_spec.json index 07d2301..04fb5f5 100644 --- a/code_generator/provider_code_spec.json +++ b/code_generator/provider_code_spec.json @@ -181,6 +181,83 @@ } ] } + }, + { + "name": "repository", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed_optional", + "default" : { + "static": "" + }, + "description": "Markdown description" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Repository name", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "namespace", + "string": { + "computed_optional_required": "required", + "description": "Repository namespace. Should be an organization name or username", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "visibility", + "string": { + "computed_optional_required": "computed_optional", + "default" : { + "static": "private" + }, + "description": "Repository visibility. Should be private or public. Defaults to private.", + "validators": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + } + ], + "schema_definition": "stringvalidator.OneOf([]string{\"private\", \"public\"}...)" + } + } + ] + } + } + ] + } } ], "datasources": [ @@ -204,6 +281,41 @@ } ] } + }, + { + "name": "repository", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed", + "description": "Markdown description" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Repository name" + } + }, + { + "name": "namespace", + "string": { + "computed_optional_required": "required", + "description": "Repository namespace. Should be an organization name or username" + } + }, + { + "name": "visibility", + "string": { + "computed_optional_required": "computed", + "description": "Repository visibility. Should be private or public." + } + } + ] + } } ], "version": "0.1" diff --git a/docs/data-sources/repository.md b/docs/data-sources/repository.md new file mode 100644 index 0000000..dfc1010 --- /dev/null +++ b/docs/data-sources/repository.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_repository Data Source - quay" +subcategory: "" +description: |- + +--- + +# quay_repository (Data Source) + + + +## Example Usage + +```terraform +data "quay_organization" "main" { + name = "main" +} + +data "quay_repository" "test" { + name = "test" + namespace = data.quay_organization.main.name +} +``` + + +## Schema + +### Required + +- `name` (String) Repository name +- `namespace` (String) Repository namespace. Should be an organization name or username + +### Read-Only + +- `description` (String) Markdown description +- `visibility` (String) Repository visibility. Should be private or public. diff --git a/docs/resources/repository.md b/docs/resources/repository.md new file mode 100644 index 0000000..e1304de --- /dev/null +++ b/docs/resources/repository.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_repository Resource - quay" +subcategory: "" +description: |- + +--- + +# quay_repository (Resource) + + + +## Example Usage + +```terraform +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.main.name + visibility = "private" + description = "test" +} +``` + + +## Schema + +### Required + +- `name` (String) Repository name +- `namespace` (String) Repository namespace. Should be an organization name or username + +### Optional + +- `description` (String) Markdown description +- `visibility` (String) Repository visibility. Should be private or public. Defaults to private. + +## Import + +Import is supported using the following syntax: + +```shell +# An organization can be imported using its name. +terraform import quay_repository.test main/test +``` diff --git a/examples/data-sources/quay_repository/data-source.tf b/examples/data-sources/quay_repository/data-source.tf new file mode 100644 index 0000000..4e3aca9 --- /dev/null +++ b/examples/data-sources/quay_repository/data-source.tf @@ -0,0 +1,8 @@ +data "quay_organization" "main" { + name = "main" +} + +data "quay_repository" "test" { + name = "test" + namespace = data.quay_organization.main.name +} diff --git a/examples/resources/quay_repository/import.sh b/examples/resources/quay_repository/import.sh new file mode 100644 index 0000000..3004a61 --- /dev/null +++ b/examples/resources/quay_repository/import.sh @@ -0,0 +1,2 @@ +# An organization can be imported using its name. +terraform import quay_repository.test main/test diff --git a/examples/resources/quay_repository/resource.tf b/examples/resources/quay_repository/resource.tf new file mode 100644 index 0000000..f1902dc --- /dev/null +++ b/examples/resources/quay_repository/resource.tf @@ -0,0 +1,11 @@ +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.main.name + visibility = "private" + description = "test" +} diff --git a/internal/datasource_repository/repository_data_source_gen.go b/internal/datasource_repository/repository_data_source_gen.go new file mode 100644 index 0000000..890719c --- /dev/null +++ b/internal/datasource_repository/repository_data_source_gen.go @@ -0,0 +1,44 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_repository + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func RepositoryDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Computed: true, + Description: "Markdown description", + MarkdownDescription: "Markdown description", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Repository name", + MarkdownDescription: "Repository name", + }, + "namespace": schema.StringAttribute{ + Required: true, + Description: "Repository namespace. Should be an organization name or username", + MarkdownDescription: "Repository namespace. Should be an organization name or username", + }, + "visibility": schema.StringAttribute{ + Computed: true, + Description: "Repository visibility. Should be private or public.", + MarkdownDescription: "Repository visibility. Should be private or public.", + }, + }, + } +} + +type RepositoryModel struct { + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` + Namespace types.String `tfsdk:"namespace"` + Visibility types.String `tfsdk:"visibility"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 05f2c88..2ed585a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -151,6 +151,7 @@ func (p *quayProvider) Metadata(_ context.Context, _ provider.MetadataRequest, r func (p *quayProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewOrganizationDataSource, + NewRepositoryDataSource, } } @@ -159,5 +160,6 @@ func (p *quayProvider) Resources(_ context.Context) []func() resource.Resource { NewOrganizationResource, NewOrganizationTeamResource, NewOrganizationRobotResource, + NewRepositoryResource, } } diff --git a/internal/provider/repository_data_source.go b/internal/provider/repository_data_source.go new file mode 100644 index 0000000..f873821 --- /dev/null +++ b/internal/provider/repository_data_source.go @@ -0,0 +1,107 @@ +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_repository" +) + +var ( + _ datasource.DataSource = (*repositoryDataSource)(nil) + _ datasource.DataSourceWithConfigure = (*repositoryDataSource)(nil) +) + +func NewRepositoryDataSource() datasource.DataSource { + return &repositoryDataSource{} +} + +type repositoryDataSource struct { + client *quay_api.APIClient +} + +func (d *repositoryDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_repository" +} + +func (d *repositoryDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_repository.RepositoryDataSourceSchema(ctx) +} + +func (d *repositoryDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data datasource_repository.RepositoryModel + + var resRepoData repositoryModelJSON + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + repoName := data.Name.ValueString() + namespace := data.Namespace.ValueString() + + // Get repository + httpRes, err := d.client.RepositoryAPI.GetRepo(context.Background(), namespace+"/"+repoName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay repository", + "Could not create Quay repository, unexpected error: "+errDetail) + return + } + + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay repository", + "Could not read Quay repository, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resRepoData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay repository", + "Could not read Quay repository, unexpected error: "+err.Error()) + return + } + + // Set visibility + if resRepoData.IsPublic { + data.Visibility = types.StringValue("public") + } else { + data.Visibility = types.StringValue("private") + } + + // Set data + data.Name = types.StringValue(resRepoData.Name) + data.Namespace = types.StringValue(resRepoData.Namespace) + data.Description = types.StringValue(resRepoData.Description) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *repositoryDataSource) 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/repository_data_source_test.go b/internal/provider/repository_data_source_test.go new file mode 100644 index 0000000..190ba08 --- /dev/null +++ b/internal/provider/repository_data_source_test.go @@ -0,0 +1,42 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccRepositoryDataSource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: providerConfig + ` +resource "quay_organization" "org_repo_data" { + name = "org_repo_data" + email = "quay+repo@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.org_repo_data.name + visibility = "private" + description = "test" +} + +data "quay_repository" "test" { + name = "test" + namespace = quay_organization.org_repo_data.name +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.quay_repository.test", "name", "test"), + resource.TestCheckResourceAttr("data.quay_repository.test", "namespace", "org_repo_data"), + resource.TestCheckResourceAttr("data.quay_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("data.quay_repository.test", "description", "test"), + ), + }, + }, + }) +} diff --git a/internal/provider/repository_resource.go b/internal/provider/repository_resource.go new file mode 100644 index 0000000..25d82e1 --- /dev/null +++ b/internal/provider/repository_resource.go @@ -0,0 +1,237 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Modifications copyright (c) Enthought, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package provider + +import ( + "context" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/types" + "io" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/enthought/terraform-provider-quay/quay_api" + "terraform-provider-quay/internal/resource_repository" +) + +var ( + _ resource.Resource = (*repositoryResource)(nil) + _ resource.ResourceWithConfigure = (*repositoryResource)(nil) + _ resource.ResourceWithImportState = (*repositoryResource)(nil) +) + +func NewRepositoryResource() resource.Resource { + return &repositoryResource{} +} + +type repositoryResource struct { + client *quay_api.APIClient +} + +type repositoryModelJSON struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` +} + +func (r *repositoryResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_repository" +} + +func (r *repositoryResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_repository.RepositoryResourceSchema(ctx) +} + +func (r *repositoryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resource_repository.RepositoryModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + repoName := data.Name.ValueString() + visibility := data.Visibility.ValueString() + namespace := data.Namespace.ValueString() + description := data.Description.ValueString() + + // Create repository + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: visibility, + Namespace: &namespace, + Description: description, + } + _, err := r.client.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay repository", "Could not create Quay repository, unexpected error: "+errDetail) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *repositoryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resource_repository.RepositoryModel + var resRepoData repositoryModelJSON + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + repoName := data.Name.ValueString() + namespace := data.Namespace.ValueString() + + // Get repository + httpRes, err := r.client.RepositoryAPI.GetRepo(context.Background(), namespace+"/"+repoName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay repository", "Could not create Quay repository, unexpected error: "+errDetail) + return + } + + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay repository", + "Could not read Quay repository, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resRepoData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay repository", + "Could not read Quay repository, unexpected error: "+err.Error()) + return + } + + // Set visibility + if resRepoData.IsPublic { + data.Visibility = types.StringValue("public") + } else { + data.Visibility = types.StringValue("private") + } + + // Set data + data.Name = types.StringValue(resRepoData.Name) + data.Namespace = types.StringValue(resRepoData.Namespace) + data.Description = types.StringValue(resRepoData.Description) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *repositoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var dataState resource_repository.RepositoryModel + var dataPlan resource_repository.RepositoryModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &dataState)...) + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &dataPlan)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + repoName := dataPlan.Name.ValueString() + visibility := dataPlan.Visibility.ValueString() + namespace := dataPlan.Namespace.ValueString() + description := dataPlan.Description.ValueString() + + // Update description + if dataPlan.Description != dataState.Description { + updateRepo := quay_api.NewRepoUpdate(description) + _, err := r.client.RepositoryAPI.UpdateRepo(context.Background(), namespace+"/"+repoName).Body(*updateRepo).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay repository", "Could not update Quay repository, unexpected error: "+errDetail) + return + } + } + + // Update visibility + if dataPlan.Visibility != dataState.Visibility { + newVisibility := quay_api.NewChangeVisibility(visibility) + _, err := r.client.RepositoryAPI.ChangeRepoVisibility(context.Background(), namespace+"/"+repoName).Body(*newVisibility).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay repository", "Could not update Quay repository, unexpected error: "+errDetail) + return + } + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &dataPlan)...) +} + +func (r *repositoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resource_repository.RepositoryModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + repoName := data.Name.ValueString() + namespace := data.Namespace.ValueString() + + _, err := r.client.RepositoryAPI.DeleteRepository(context.Background(), namespace+"/"+repoName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error deleting Quay repository", "Could not delete Quay repository, unexpected error: "+errDetail) + return + } +} + +func (r *repositoryResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*quay_api.APIClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *quay_api.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + + r.client = client +} + +func (r *repositoryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idSplit := strings.Split(req.ID, "/") + if len(idSplit) != 2 || idSplit[0] == "" || idSplit[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format namespace/repository. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("namespace"), idSplit[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idSplit[1])...) +} diff --git a/internal/provider/repository_resource_test.go b/internal/provider/repository_resource_test.go new file mode 100644 index 0000000..4eb9cef --- /dev/null +++ b/internal/provider/repository_resource_test.go @@ -0,0 +1,109 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccRepositoryResource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: providerConfig + ` +resource "quay_organization" "org_repo" { + name = "org_repo" + email = "quay+repo@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.org_repo.name + visibility = "private" + description = "test" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_repository.test", "name", "test"), + resource.TestCheckResourceAttr("quay_repository.test", "namespace", "org_repo"), + resource.TestCheckResourceAttr("quay_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("quay_repository.test", "description", "test"), + ), + }, + // Import + { + ResourceName: "quay_repository.test", + ImportState: true, + ImportStateId: "org_repo/test", + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "name", + }, + // Update + { + Config: providerConfig + ` +resource "quay_organization" "org_repo" { + name = "org_repo" + email = "quay+repo@example.com" +} + +resource "quay_repository" "test" { + name = "test" + namespace = quay_organization.org_repo.name + visibility = "public" + description = "test2" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_repository.test", "name", "test"), + resource.TestCheckResourceAttr("quay_repository.test", "namespace", "org_repo"), + resource.TestCheckResourceAttr("quay_repository.test", "visibility", "public"), + resource.TestCheckResourceAttr("quay_repository.test", "description", "test2"), + ), + }, + // Replace resource + { + Config: providerConfig + ` +resource "quay_organization" "org_repo" { + name = "org_repo" + email = "quay+repo@example.com" +} + +resource "quay_repository" "test" { + name = "test2" + namespace = quay_organization.org_repo.name + visibility = "public" + description = "test2" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_repository.test", "name", "test2"), + resource.TestCheckResourceAttr("quay_repository.test", "namespace", "org_repo"), + resource.TestCheckResourceAttr("quay_repository.test", "visibility", "public"), + resource.TestCheckResourceAttr("quay_repository.test", "description", "test2"), + ), + }, + // Create with only required values + { + Config: providerConfig + ` +resource "quay_organization" "org_repo" { + name = "org_repo" + email = "quay+repo@example.com" +} + +resource "quay_repository" "test3" { + name = "test3" + namespace = quay_organization.org_repo.name +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_repository.test3", "name", "test3"), + resource.TestCheckResourceAttr("quay_repository.test3", "namespace", "org_repo"), + resource.TestCheckResourceAttr("quay_repository.test3", "visibility", "private"), + resource.TestCheckResourceAttr("quay_repository.test3", "description", ""), + ), + }, + }, + }) +} diff --git a/internal/resource_repository/repository_resource_gen.go b/internal/resource_repository/repository_resource_gen.go new file mode 100644 index 0000000..fabdda4 --- /dev/null +++ b/internal/resource_repository/repository_resource_gen.go @@ -0,0 +1,62 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_repository + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func RepositoryResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Markdown description", + MarkdownDescription: "Markdown description", + Default: stringdefault.StaticString(""), + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Repository name", + MarkdownDescription: "Repository name", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "namespace": schema.StringAttribute{ + Required: true, + Description: "Repository namespace. Should be an organization name or username", + MarkdownDescription: "Repository namespace. Should be an organization name or username", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "visibility": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Repository visibility. Should be private or public. Defaults to private.", + MarkdownDescription: "Repository visibility. Should be private or public. Defaults to private.", + Validators: []validator.String{ + stringvalidator.OneOf([]string{"private", "public"}...), + }, + Default: stringdefault.StaticString("private"), + }, + }, + } +} + +type RepositoryModel struct { + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` + Namespace types.String `tfsdk:"namespace"` + Visibility types.String `tfsdk:"visibility"` +} diff --git a/quay_api/test/api_repository_test.go b/quay_api/test/api_repository_test.go index 783d69c..2055a1d 100644 --- a/quay_api/test/api_repository_test.go +++ b/quay_api/test/api_repository_test.go @@ -1,75 +1,145 @@ -/* -Quay Frontend - -Testing RepositoryAPIService - -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); - package quay_api import ( "context" - openapiclient "github.com/enthought/terraform-provider-quay/quay_api" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + + "github.com/enthought/terraform-provider-quay/quay_api" ) func Test_quay_api_RepositoryAPIService(t *testing.T) { - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) + configuration := newConfiguration() + apiClient := quay_api.NewAPIClient(configuration) t.Run("Test RepositoryAPIService ChangeRepoVisibility", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var repository string - - httpRes, err := apiClient.RepositoryAPI.ChangeRepoVisibility(context.Background(), repository).Execute() - - require.Nil(t, err) + orgName := "change-repo-visibility" + repoName := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create repo + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: "private", + Namespace: &orgName, + Description: "test", + } + httpRes, err = apiClient.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Change repo visibility + newVisibility := quay_api.NewChangeVisibility("public") + httpRes, err = apiClient.RepositoryAPI.ChangeRepoVisibility(context.Background(), orgName+"/"+repoName).Body(*newVisibility).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) t.Run("Test RepositoryAPIService CreateRepo", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - httpRes, err := apiClient.RepositoryAPI.CreateRepo(context.Background()).Execute() - - require.Nil(t, err) - assert.Equal(t, 200, httpRes.StatusCode) - + orgName := "create-repo" + repoName := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create repo + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: "private", + Namespace: &orgName, + Description: "test", + } + httpRes, err = apiClient.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) }) t.Run("Test RepositoryAPIService DeleteRepository", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var repository string - - httpRes, err := apiClient.RepositoryAPI.DeleteRepository(context.Background(), repository).Execute() - - require.Nil(t, err) - assert.Equal(t, 200, httpRes.StatusCode) - + orgName := "delete-repo" + repoName := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create repo + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: "private", + Namespace: &orgName, + Description: "test", + } + httpRes, err = apiClient.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Delete repo + httpRes, err = apiClient.RepositoryAPI.DeleteRepository(context.Background(), orgName+"/"+repoName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 204, httpRes.StatusCode) }) t.Run("Test RepositoryAPIService GetRepo", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var repository string - - httpRes, err := apiClient.RepositoryAPI.GetRepo(context.Background(), repository).Execute() - - require.Nil(t, err) + orgName := "get-repo" + repoName := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create repo + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: "private", + Namespace: &orgName, + Description: "test", + } + httpRes, err = apiClient.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Get repo + httpRes, err = apiClient.RepositoryAPI.GetRepo(context.Background(), orgName+"/"+repoName).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) t.Run("Test RepositoryAPIService ListRepos", func(t *testing.T) { @@ -84,16 +154,36 @@ func Test_quay_api_RepositoryAPIService(t *testing.T) { }) t.Run("Test RepositoryAPIService UpdateRepo", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var repository string - - httpRes, err := apiClient.RepositoryAPI.UpdateRepo(context.Background(), repository).Execute() - - require.Nil(t, err) + orgName := "update-repo" + repoName := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create repo + newRepo := quay_api.NewRepo{ + Repository: repoName, + Visibility: "private", + Namespace: &orgName, + Description: "test", + } + httpRes, err = apiClient.RepositoryAPI.CreateRepo(context.Background()).Body(newRepo).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Update repo + newRepoUpdate := quay_api.NewRepoUpdate("test2") + httpRes, err = apiClient.RepositoryAPI.UpdateRepo(context.Background(), orgName+"/"+repoName).Body(*newRepoUpdate).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) - }