From 2ae34968977e1f5587cb35c528a1b6b9af12e3a0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jul 2024 11:14:40 -0700 Subject: [PATCH 1/8] Upgrade Go version to 1.22 --- .github/workflows/release.yml | 10 +++++----- .goreleaser.yml | 2 ++ go.mod | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13d71ece..43103c3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,25 +22,25 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5.0.0 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: version: ${{ github.event.inputs.tag }} args: release --clean diff --git a/.goreleaser.yml b/.goreleaser.yml index c1f2fb39..858bde04 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + # Visit https://goreleaser.com for documentation on how to customize this # behavior. before: diff --git a/go.mod b/go.mod index 3117b91d..9c1d4787 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/jfrog/terraform-provider-project // if you need to do local dev, literally just uncomment the line below // replace github.com/jfrog/terraform-provider-shared => ../terraform-provider-shared -go 1.21.5 +go 1.22.5 require ( github.com/go-resty/resty/v2 v2.13.1 From 3b1f2617c6e52568107cbcc7fe084257dcf111d8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jul 2024 11:16:07 -0700 Subject: [PATCH 2/8] Move provide code location --- pkg/project/acctest/test.go | 2 +- pkg/project/{provider => }/framework.go | 0 pkg/project/{provider => }/provider.go | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename pkg/project/{provider => }/framework.go (100%) rename pkg/project/{provider => }/provider.go (100%) diff --git a/pkg/project/acctest/test.go b/pkg/project/acctest/test.go index 2d8d78ad..57c45afd 100644 --- a/pkg/project/acctest/test.go +++ b/pkg/project/acctest/test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/terraform" - project "github.com/jfrog/terraform-provider-project/pkg/project/provider" + project "github.com/jfrog/terraform-provider-project/pkg/project" "github.com/jfrog/terraform-provider-shared/client" "github.com/jfrog/terraform-provider-shared/testutil" ) diff --git a/pkg/project/provider/framework.go b/pkg/project/framework.go similarity index 100% rename from pkg/project/provider/framework.go rename to pkg/project/framework.go diff --git a/pkg/project/provider/provider.go b/pkg/project/provider.go similarity index 100% rename from pkg/project/provider/provider.go rename to pkg/project/provider.go From bda87590176889297f2e762c6a1e7a0aaf43f6ef Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jul 2024 11:18:20 -0700 Subject: [PATCH 3/8] Consolidate provider code and rename package --- main.go | 4 +- pkg/project/framework.go | 205 -------------------------------------- pkg/project/provider.go | 206 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 208 deletions(-) delete mode 100644 pkg/project/framework.go diff --git a/main.go b/main.go index fee77772..85f27f2d 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" - provider "github.com/jfrog/terraform-provider-project/pkg/project/provider" + "github.com/jfrog/terraform-provider-project/pkg/project" ) // Run the docs generation tool, check its repository for more information on how it works and how docs @@ -24,7 +24,7 @@ func main() { Address: "registry.terraform.io/jfrog/project", Debug: debug, } - err := providerserver.Serve(context.Background(), provider.Framework(), opts) + err := providerserver.Serve(context.Background(), project.Framework(), opts) if err != nil { log.Fatal(err.Error()) } diff --git a/pkg/project/framework.go b/pkg/project/framework.go deleted file mode 100644 index b8bc4f25..00000000 --- a/pkg/project/framework.go +++ /dev/null @@ -1,205 +0,0 @@ -package provider - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/provider" - "github.com/hashicorp/terraform-plugin-framework/provider/schema" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - project "github.com/jfrog/terraform-provider-project/pkg/project/resource" - "github.com/jfrog/terraform-provider-shared/client" - "github.com/jfrog/terraform-provider-shared/util" - validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" -) - -// Ensure the implementation satisfies the provider.Provider interface. -var _ provider.Provider = &ProjectProvider{} - -type ProjectProvider struct { - Meta util.ProviderMetadata -} - -// ProjectProviderModel describes the provider data model. -type ProjectProviderModel struct { - Url types.String `tfsdk:"url"` - AccessToken types.String `tfsdk:"access_token"` - OIDCProviderName types.String `tfsdk:"oidc_provider_name"` - CheckLicense types.Bool `tfsdk:"check_license"` -} - -// Metadata satisfies the provider.Provider interface for ProjectProvider -func (p *ProjectProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "project" - resp.Version = Version -} - -// Schema satisfies the provider.Provider interface for ProjectProvider. -func (p *ProjectProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - validatorfw_string.IsURLHttpOrHttps(), - }, - Description: "URL of Artifactory. This can also be sourced from the `PROJECT_URL` or `JFROG_URL` environment variable. Default to 'http://localhost:8081' if not set.", - }, - "access_token": schema.StringAttribute{ - Optional: true, - Sensitive: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - Description: "This is a Bearer token that can be given to you by your admin under `Identity and Access`. This can also be sourced from the `PROJECT_ACCESS_TOKEN` or `JFROG_ACCESS_TOKEN` environment variable. Defauult to empty string if not set.", - }, - "oidc_provider_name": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - Description: "OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.", - }, - "check_license": schema.BoolAttribute{ - Optional: true, - Description: "Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`.", - }, - }, - } -} - -func (p *ProjectProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - // Check environment variables, first available OS variable will be assigned to the var - url := util.CheckEnvVars([]string{"JFROG_URL", "PROJECT_URL"}, "") - accessToken := util.CheckEnvVars([]string{"JFROG_ACCESS_TOKEN", "PROJECT_ACCESS_TOKEN"}, "") - - var config ProjectProviderModel - - // Read configuration data into model - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - if config.Url.ValueString() != "" { - url = config.Url.ValueString() - } - - if url == "" { - resp.Diagnostics.AddError( - "Missing URL Configuration", - "While configuring the provider, the url was not found in "+ - "the JFROG_URL/ARTIFACTORY_URL environment variable or provider "+ - "configuration block url attribute.", - ) - return - } - - restyClient, err := client.Build(url, productId) - if err != nil { - resp.Diagnostics.AddError( - "Error creating Resty client", - err.Error(), - ) - return - } - - oidcAccessToken, err := util.OIDCTokenExchange(ctx, restyClient, config.OIDCProviderName.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Failed OIDC ID token exchange", - err.Error(), - ) - return - } - - // use token from OIDC provider, which should take precedence over - // environment variable data, if found. - if oidcAccessToken != "" { - accessToken = oidcAccessToken - } - - // Check configuration data, which should take precedence over - // environment variable data or OIDC access token, if found. - if config.AccessToken.ValueString() != "" { - accessToken = config.AccessToken.ValueString() - } - - if accessToken == "" { - resp.Diagnostics.AddError( - "Missing JFrog Access Token", - "While configuring the provider, the Access Token was not found in "+ - "the JFROG_ACCESS_TOKEN/PROJECT_ACCESS_TOKEN environment variable or provider "+ - "configuration block access_token attribute.", - ) - return - } - - restyClient, err = client.AddAuth(restyClient, "", accessToken) - if err != nil { - resp.Diagnostics.AddError( - "Error adding Auth to Resty client", - err.Error(), - ) - } - - if config.CheckLicense.IsNull() || config.CheckLicense.ValueBool() { - if err := util.CheckArtifactoryLicense(restyClient, "Enterprise", "Commercial", "Edge"); err != nil { - resp.Diagnostics.AddError( - "Error checking Artifactory license", - err.Error(), - ) - return - } - } - - version, err := util.GetArtifactoryVersion(restyClient) - if err != nil { - resp.Diagnostics.AddError( - "Error getting Artifactory version", - fmt.Sprintf("The provider functionality might be affected by the absence of Artifactory version in the context. %v", err), - ) - return - } - - featureUsage := fmt.Sprintf("Terraform/%s", req.TerraformVersion) - go util.SendUsage(ctx, restyClient.R(), productId, featureUsage) - - meta := util.ProviderMetadata{ - Client: restyClient, - ProductId: productId, - ArtifactoryVersion: version, - } - - p.Meta = meta - - resp.DataSourceData = meta - resp.ResourceData = meta -} - -// Resources satisfies the provider.Provider interface for ProjectProvider. -func (p *ProjectProvider) Resources(ctx context.Context) []func() resource.Resource { - return []func() resource.Resource{ - project.NewProjectResource, - project.NewProjectEnvironmentResource, - project.NewProjectGroupResource, - project.NewProjectRepositoryResource, - project.NewProjectRoleResource, - project.NewProjectUserResource, - } -} - -// DataSources satisfies the provider.Provider interface for ProjectProvider. -func (p *ProjectProvider) DataSources(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} -} - -func Framework() func() provider.Provider { - return func() provider.Provider { - return &ProjectProvider{} - } -} diff --git a/pkg/project/provider.go b/pkg/project/provider.go index 793765f5..691c49ba 100644 --- a/pkg/project/provider.go +++ b/pkg/project/provider.go @@ -1,6 +1,210 @@ -package provider +package project + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + project "github.com/jfrog/terraform-provider-project/pkg/project/resource" + "github.com/jfrog/terraform-provider-shared/client" + "github.com/jfrog/terraform-provider-shared/util" + validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" +) var Version = "1.6.1" // needs to be exported so make file can update this var productId = "terraform-provider-project/" + Version + +// Ensure the implementation satisfies the provider.Provider interface. +var _ provider.Provider = &ProjectProvider{} + +type ProjectProvider struct { + Meta util.ProviderMetadata +} + +// ProjectProviderModel describes the provider data model. +type ProjectProviderModel struct { + Url types.String `tfsdk:"url"` + AccessToken types.String `tfsdk:"access_token"` + OIDCProviderName types.String `tfsdk:"oidc_provider_name"` + CheckLicense types.Bool `tfsdk:"check_license"` +} + +// Metadata satisfies the provider.Provider interface for ProjectProvider +func (p *ProjectProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "project" + resp.Version = Version +} + +// Schema satisfies the provider.Provider interface for ProjectProvider. +func (p *ProjectProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + validatorfw_string.IsURLHttpOrHttps(), + }, + Description: "URL of Artifactory. This can also be sourced from the `PROJECT_URL` or `JFROG_URL` environment variable. Default to 'http://localhost:8081' if not set.", + }, + "access_token": schema.StringAttribute{ + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Description: "This is a Bearer token that can be given to you by your admin under `Identity and Access`. This can also be sourced from the `PROJECT_ACCESS_TOKEN` or `JFROG_ACCESS_TOKEN` environment variable. Defauult to empty string if not set.", + }, + "oidc_provider_name": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Description: "OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.", + }, + "check_license": schema.BoolAttribute{ + Optional: true, + Description: "Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`.", + }, + }, + } +} + +func (p *ProjectProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Check environment variables, first available OS variable will be assigned to the var + url := util.CheckEnvVars([]string{"JFROG_URL", "PROJECT_URL"}, "") + accessToken := util.CheckEnvVars([]string{"JFROG_ACCESS_TOKEN", "PROJECT_ACCESS_TOKEN"}, "") + + var config ProjectProviderModel + + // Read configuration data into model + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.Url.ValueString() != "" { + url = config.Url.ValueString() + } + + if url == "" { + resp.Diagnostics.AddError( + "Missing URL Configuration", + "While configuring the provider, the url was not found in "+ + "the JFROG_URL/ARTIFACTORY_URL environment variable or provider "+ + "configuration block url attribute.", + ) + return + } + + restyClient, err := client.Build(url, productId) + if err != nil { + resp.Diagnostics.AddError( + "Error creating Resty client", + err.Error(), + ) + return + } + + oidcAccessToken, err := util.OIDCTokenExchange(ctx, restyClient, config.OIDCProviderName.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Failed OIDC ID token exchange", + err.Error(), + ) + return + } + + // use token from OIDC provider, which should take precedence over + // environment variable data, if found. + if oidcAccessToken != "" { + accessToken = oidcAccessToken + } + + // Check configuration data, which should take precedence over + // environment variable data or OIDC access token, if found. + if config.AccessToken.ValueString() != "" { + accessToken = config.AccessToken.ValueString() + } + + if accessToken == "" { + resp.Diagnostics.AddError( + "Missing JFrog Access Token", + "While configuring the provider, the Access Token was not found in "+ + "the JFROG_ACCESS_TOKEN/PROJECT_ACCESS_TOKEN environment variable or provider "+ + "configuration block access_token attribute.", + ) + return + } + + restyClient, err = client.AddAuth(restyClient, "", accessToken) + if err != nil { + resp.Diagnostics.AddError( + "Error adding Auth to Resty client", + err.Error(), + ) + } + + if config.CheckLicense.IsNull() || config.CheckLicense.ValueBool() { + if err := util.CheckArtifactoryLicense(restyClient, "Enterprise", "Commercial", "Edge"); err != nil { + resp.Diagnostics.AddError( + "Error checking Artifactory license", + err.Error(), + ) + return + } + } + + version, err := util.GetArtifactoryVersion(restyClient) + if err != nil { + resp.Diagnostics.AddError( + "Error getting Artifactory version", + fmt.Sprintf("The provider functionality might be affected by the absence of Artifactory version in the context. %v", err), + ) + return + } + + featureUsage := fmt.Sprintf("Terraform/%s", req.TerraformVersion) + go util.SendUsage(ctx, restyClient.R(), productId, featureUsage) + + meta := util.ProviderMetadata{ + Client: restyClient, + ProductId: productId, + ArtifactoryVersion: version, + } + + p.Meta = meta + + resp.DataSourceData = meta + resp.ResourceData = meta +} + +// Resources satisfies the provider.Provider interface for ProjectProvider. +func (p *ProjectProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + project.NewProjectResource, + project.NewProjectEnvironmentResource, + project.NewProjectGroupResource, + project.NewProjectRepositoryResource, + project.NewProjectRoleResource, + project.NewProjectUserResource, + } +} + +// DataSources satisfies the provider.Provider interface for ProjectProvider. +func (p *ProjectProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} + +func Framework() func() provider.Provider { + return func() provider.Provider { + return &ProjectProvider{} + } +} From b31fecfb6321b29bd040ffdb0562727ed4291c67 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jul 2024 11:19:37 -0700 Subject: [PATCH 4/8] Rename provider func --- main.go | 2 +- pkg/project/acctest/test.go | 4 ++-- pkg/project/provider.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 85f27f2d..99ad8172 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ func main() { Address: "registry.terraform.io/jfrog/project", Debug: debug, } - err := providerserver.Serve(context.Background(), project.Framework(), opts) + err := providerserver.Serve(context.Background(), project.NewProvider(), opts) if err != nil { log.Fatal(err.Error()) } diff --git a/pkg/project/acctest/test.go b/pkg/project/acctest/test.go index 57c45afd..8951966d 100644 --- a/pkg/project/acctest/test.go +++ b/pkg/project/acctest/test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/terraform" - project "github.com/jfrog/terraform-provider-project/pkg/project" + "github.com/jfrog/terraform-provider-project/pkg/project" "github.com/jfrog/terraform-provider-shared/client" "github.com/jfrog/terraform-provider-shared/testutil" ) @@ -31,7 +31,7 @@ var Provider provider.Provider var ProtoV6ProviderFactories map[string]func() (tfprotov6.ProviderServer, error) func init() { - Provider = project.Framework()() + Provider = project.NewProvider()() ProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "project": providerserver.NewProtocol6WithError(Provider), diff --git a/pkg/project/provider.go b/pkg/project/provider.go index 691c49ba..3ad3f4d8 100644 --- a/pkg/project/provider.go +++ b/pkg/project/provider.go @@ -203,7 +203,7 @@ func (p *ProjectProvider) DataSources(_ context.Context) []func() datasource.Dat return []func() datasource.DataSource{} } -func Framework() func() provider.Provider { +func NewProvider() func() provider.Provider { return func() provider.Provider { return &ProjectProvider{} } From fc76dc4e6da68cc6a9db5149c66c7a3b02008fcb Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 23 Jul 2024 12:23:44 -0700 Subject: [PATCH 5/8] Add new resources for sharing repo with projects Deprecate provider attribute 'check_license' Fix acceptance tests Fix resource type names. Mostly for usage request purpose. --- docs/index.md | 7 +- docs/resources/share_repository.md | 38 +++ docs/resources/share_repository_with_all.md | 36 +++ .../project_share_repository/import.sh | 1 + .../project_share_repository/resource.tf | 4 + .../import.sh | 1 + .../resource.tf | 3 + pkg/project/provider.go | 17 +- pkg/project/resource/membership_test.go | 9 +- pkg/project/resource/resource_project.go | 5 +- .../resource/resource_project_environment.go | 7 +- .../resource/resource_project_group.go | 7 +- .../resource/resource_project_repository.go | 91 ++++-- pkg/project/resource/resource_project_role.go | 7 +- .../resource_project_share_repository.go | 261 ++++++++++++++++++ .../resource_project_share_repository_test.go | 144 ++++++++++ ...ource_project_share_repository_with_all.go | 208 ++++++++++++++ ..._project_share_repository_with_all_test.go | 101 +++++++ pkg/project/resource/resource_project_test.go | 25 +- pkg/project/resource/resource_project_user.go | 7 +- .../resource/resource_project_user_test.go | 3 +- pkg/project/resource/util.go | 11 + 22 files changed, 919 insertions(+), 74 deletions(-) create mode 100644 docs/resources/share_repository.md create mode 100644 docs/resources/share_repository_with_all.md create mode 100644 examples/resources/project_share_repository/import.sh create mode 100644 examples/resources/project_share_repository/resource.tf create mode 100644 examples/resources/project_share_repository_with_all/import.sh create mode 100644 examples/resources/project_share_repository_with_all/resource.tf create mode 100644 pkg/project/resource/resource_project_share_repository.go create mode 100644 pkg/project/resource/resource_project_share_repository_test.go create mode 100644 pkg/project/resource/resource_project_share_repository_with_all.go create mode 100644 pkg/project/resource/resource_project_share_repository_with_all_test.go diff --git a/docs/index.md b/docs/index.md index 0f27e950..3fb55ddb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -250,12 +250,9 @@ provider "project" { ## Schema -### Required - -- `url` (String) URL of Artifactory. This can also be sourced from the `PROJECT_URL` or `JFROG_URL` environment variable. Default to 'http://localhost:8081' if not set. - ### Optional - `access_token` (String, Sensitive) This is a Bearer token that can be given to you by your admin under `Identity and Access`. This can also be sourced from the `PROJECT_ACCESS_TOKEN` or `JFROG_ACCESS_TOKEN` environment variable. Defauult to empty string if not set. -- `check_license` (Boolean) Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`. +- `check_license` (Boolean, Deprecated) Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`. - `oidc_provider_name` (String) OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details. +- `url` (String) URL of Artifactory. This can also be sourced from the `PROJECT_URL` or `JFROG_URL` environment variable. Default to 'http://localhost:8081' if not set. diff --git a/docs/resources/share_repository.md b/docs/resources/share_repository.md new file mode 100644 index 00000000..7c2bbc49 --- /dev/null +++ b/docs/resources/share_repository.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "project_share_repository Resource - terraform-provider-project" +subcategory: "" +description: |- + Share a local or remote repository with a list of projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role. Only available for Artifactory 7.90.1 or later. +--- + +# project_share_repository (Resource) + +Share a local or remote repository with a list of projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role. + +->Only available for Artifactory 7.90.1 or later. + +## Example Usage + +```terraform +resource "project_share_repository" "myprojectsharerepo" { + repo_key = "myrepo-generic-local" + target_project_key = "myproj" +} +``` + + +## Schema + +### Required + +- `repo_key` (String) The key of the repository. +- `target_project_key` (String) The project key to which the repository should be shared with. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import project_share_repository.myprojectsharerepo repo_key:project_key +``` diff --git a/docs/resources/share_repository_with_all.md b/docs/resources/share_repository_with_all.md new file mode 100644 index 00000000..d85e51b0 --- /dev/null +++ b/docs/resources/share_repository_with_all.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "project_share_repository_with_all Resource - terraform-provider-project" +subcategory: "" +description: |- + Share a local or remote repository with all projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role. Only available for Artifactory 7.90.1 or later. +--- + +# project_share_repository_with_all (Resource) + +Share a local or remote repository with all projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role. + +->Only available for Artifactory 7.90.1 or later. + +## Example Usage + +```terraform +resource "project_share_repository_with_all" "myprojectsharerepo" { + repo_key = "myrepo-generic-local" +} +``` + + +## Schema + +### Required + +- `repo_key` (String) The key of the repository. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import project_share_repository_with_all.myprojectsharerepo repo_key +``` diff --git a/examples/resources/project_share_repository/import.sh b/examples/resources/project_share_repository/import.sh new file mode 100644 index 00000000..12fe8aac --- /dev/null +++ b/examples/resources/project_share_repository/import.sh @@ -0,0 +1 @@ +terraform import project_share_repository.myprojectsharerepo repo_key:project_key \ No newline at end of file diff --git a/examples/resources/project_share_repository/resource.tf b/examples/resources/project_share_repository/resource.tf new file mode 100644 index 00000000..c9f1bb0c --- /dev/null +++ b/examples/resources/project_share_repository/resource.tf @@ -0,0 +1,4 @@ +resource "project_share_repository" "myprojectsharerepo" { + repo_key = "myrepo-generic-local" + target_project_key = "myproj" +} \ No newline at end of file diff --git a/examples/resources/project_share_repository_with_all/import.sh b/examples/resources/project_share_repository_with_all/import.sh new file mode 100644 index 00000000..81a6b948 --- /dev/null +++ b/examples/resources/project_share_repository_with_all/import.sh @@ -0,0 +1 @@ +terraform import project_share_repository_with_all.myprojectsharerepo repo_key \ No newline at end of file diff --git a/examples/resources/project_share_repository_with_all/resource.tf b/examples/resources/project_share_repository_with_all/resource.tf new file mode 100644 index 00000000..da1e5668 --- /dev/null +++ b/examples/resources/project_share_repository_with_all/resource.tf @@ -0,0 +1,3 @@ +resource "project_share_repository_with_all" "myprojectsharerepo" { + repo_key = "myrepo-generic-local" +} \ No newline at end of file diff --git a/pkg/project/provider.go b/pkg/project/provider.go index 3ad3f4d8..3e4e7dfe 100644 --- a/pkg/project/provider.go +++ b/pkg/project/provider.go @@ -70,8 +70,9 @@ func (p *ProjectProvider) Schema(ctx context.Context, req provider.SchemaRequest Description: "OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.", }, "check_license": schema.BoolAttribute{ - Optional: true, - Description: "Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`.", + Optional: true, + Description: "Toggle for pre-flight checking of Artifactory Enterprise license. Default to `true`.", + DeprecationMessage: "Remove this attribute from your provider configuration as it is no longer used and the attribute will be removed in the next major version of the provider.", }, }, } @@ -152,16 +153,6 @@ func (p *ProjectProvider) Configure(ctx context.Context, req provider.ConfigureR ) } - if config.CheckLicense.IsNull() || config.CheckLicense.ValueBool() { - if err := util.CheckArtifactoryLicense(restyClient, "Enterprise", "Commercial", "Edge"); err != nil { - resp.Diagnostics.AddError( - "Error checking Artifactory license", - err.Error(), - ) - return - } - } - version, err := util.GetArtifactoryVersion(restyClient) if err != nil { resp.Diagnostics.AddError( @@ -194,6 +185,8 @@ func (p *ProjectProvider) Resources(ctx context.Context) []func() resource.Resou project.NewProjectGroupResource, project.NewProjectRepositoryResource, project.NewProjectRoleResource, + project.NewProjectShareRepositoryResource, + project.NewProjectShareRepositoryWithAllResource, project.NewProjectUserResource, } } diff --git a/pkg/project/resource/membership_test.go b/pkg/project/resource/membership_test.go index 4e173f26..9eb311b8 100644 --- a/pkg/project/resource/membership_test.go +++ b/pkg/project/resource/membership_test.go @@ -15,8 +15,8 @@ func TestAccProject_membership(t *testing.T) { resourceName := "project." + name projectKey := strings.ToLower(acctest.RandSeq(10)) - username1 := "user1" - username2 := "user2" + username1 := fmt.Sprintf("user1%s", strings.ToLower(acctest.RandSeq(5))) + username2 := fmt.Sprintf("user2%s", strings.ToLower(acctest.RandSeq(5))) developeRole := "Developer" contributorRole := "Contributor" @@ -174,8 +174,9 @@ func TestAccProject_group(t *testing.T) { resourceName := "project." + name projectKey := strings.ToLower(acctest.RandSeq(10)) - group1 := "group1" - group2 := "group2" + group1 := fmt.Sprintf("group1%s", strings.ToLower(acctest.RandSeq(5))) + group2 := fmt.Sprintf("group2%s", strings.ToLower(acctest.RandSeq(5))) + developeRole := "Developer" contributorRole := "Contributor" diff --git a/pkg/project/resource/resource_project.go b/pkg/project/resource/resource_project.go index b8e20f96..bd3a940c 100644 --- a/pkg/project/resource/resource_project.go +++ b/pkg/project/resource/resource_project.go @@ -38,7 +38,9 @@ const ( var customRoleTypeRegex = regexp.MustCompile(fmt.Sprintf("^%s$", customRoleType)) func NewProjectResource() resource.Resource { - return &ProjectResource{} + return &ProjectResource{ + TypeName: "project", + } } type ProjectResource struct { @@ -401,7 +403,6 @@ type ProjectAPIModel struct { func (r *ProjectResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName - r.TypeName = resp.TypeName } var schemaV1 = schema.Schema{ diff --git a/pkg/project/resource/resource_project_environment.go b/pkg/project/resource/resource_project_environment.go index 72c212c6..f0f47546 100644 --- a/pkg/project/resource/resource_project_environment.go +++ b/pkg/project/resource/resource_project_environment.go @@ -24,7 +24,9 @@ import ( const ProjectEnvironmentUrl = "/access/api/v1/projects/{projectKey}/environments" func NewProjectEnvironmentResource() resource.Resource { - return &ProjectEnvironmentResource{} + return &ProjectEnvironmentResource{ + TypeName: "project_environment", + } } type ProjectEnvironmentResource struct { @@ -47,8 +49,7 @@ type ProjectEnvironmentUpdateAPIModel struct { } func (r *ProjectEnvironmentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_environment" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } func (r *ProjectEnvironmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/pkg/project/resource/resource_project_group.go b/pkg/project/resource/resource_project_group.go index bffe6a31..1f5779ea 100644 --- a/pkg/project/resource/resource_project_group.go +++ b/pkg/project/resource/resource_project_group.go @@ -23,7 +23,9 @@ import ( const ProjectGroupsUrl = "access/api/v1/projects/{projectKey}/groups/{name}" func NewProjectGroupResource() resource.Resource { - return &ProjectGroupResource{} + return &ProjectGroupResource{ + TypeName: "project_group", + } } type ProjectGroupResource struct { @@ -44,8 +46,7 @@ type ProjectGroupAPIModel struct { } func (r *ProjectGroupResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_group" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } func (r *ProjectGroupResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/pkg/project/resource/resource_project_repository.go b/pkg/project/resource/resource_project_repository.go index 04fcb229..fd9ffe8b 100644 --- a/pkg/project/resource/resource_project_repository.go +++ b/pkg/project/resource/resource_project_repository.go @@ -24,7 +24,9 @@ import ( const repositoryEndpoint = "/artifactory/api/repositories/{key}" func NewProjectRepositoryResource() resource.Resource { - return &ProjectRepositoryResource{} + return &ProjectRepositoryResource{ + TypeName: "project_repository", + } } type ProjectRepositoryResource struct { @@ -44,8 +46,7 @@ type ProjectRepositoryAPIModel struct { } func (r *ProjectRepositoryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_repository" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } func (r *ProjectRepositoryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -169,29 +170,73 @@ func (r *ProjectRepositoryResource) Read(ctx context.Context, req resource.ReadR projectKey := state.ProjectKey.ValueString() repoKey := state.Key.ValueString() - var repo ProjectRepositoryAPIModel - var projectError ProjectErrorsResponse - response, err := r.ProviderData.Client.R(). - SetResult(&repo). - SetPathParam("key", repoKey). - Get(repositoryEndpoint) + newAPIVersion, err := util.CheckVersion(r.ProviderData.ArtifactoryVersion, "7.90.1") if err != nil { - utilfw.UnableToRefreshResourceError(resp, err.Error()) - return + resp.Diagnostics.AddError( + "Failed to check Artifactory version", + err.Error(), + ) } - if response.StatusCode() == http.StatusBadRequest || response.StatusCode() == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - if response.IsError() { - utilfw.UnableToRefreshResourceError(resp, projectError.String()) - return - } - if repo.ProjectKey == "" { - tflog.Warn(ctx, "no project_key for repo", map[string]any{"repoKey": repoKey}) - resp.State.RemoveResource(ctx) - return + var projectError ProjectErrorsResponse + if newAPIVersion { + // use new project API + var status ProjectRepositoryStatusAPIModel + response, err := r.ProviderData.Client.R(). + SetResult(&status). + SetPathParam("repo_key", repoKey). + Get(ProjectRepositoryStatusEndpoint) + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, projectError.String()) + return + } + + if status.AssignedTo != projectKey { + tflog.Warn(ctx, "repo not assigned to project", map[string]any{ + "repoKey": repoKey, + "projectKey": projectKey, + }) + resp.State.RemoveResource(ctx) + return + } + } else { + // continue using old repo API for checking + var repo ProjectRepositoryAPIModel + response, err := r.ProviderData.Client.R(). + SetResult(&repo). + SetPathParam("key", repoKey). + Get(repositoryEndpoint) + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } + + if response.StatusCode() == http.StatusBadRequest || response.StatusCode() == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, projectError.String()) + return + } + if repo.ProjectKey == "" { + tflog.Warn(ctx, "no project_key for repo", map[string]any{ + "repoKey": repoKey, + "projectKey": projectKey, + }) + resp.State.RemoveResource(ctx) + return + } } state.ID = types.StringValue(fmt.Sprintf("%s-%s", projectKey, repoKey)) diff --git a/pkg/project/resource/resource_project_role.go b/pkg/project/resource/resource_project_role.go index 76d1a5aa..ba8dc0d0 100644 --- a/pkg/project/resource/resource_project_role.go +++ b/pkg/project/resource/resource_project_role.go @@ -65,7 +65,9 @@ var validRoleActions = []string{ } func NewProjectRoleResource() resource.Resource { - return &ProjectRoleResource{} + return &ProjectRoleResource{ + TypeName: "project_role", + } } type ProjectRoleResource struct { @@ -90,8 +92,7 @@ type ProjectRoleAPIModel struct { } func (r *ProjectRoleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_role" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } func (r *ProjectRoleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/pkg/project/resource/resource_project_share_repository.go b/pkg/project/resource/resource_project_share_repository.go new file mode 100644 index 00000000..a989c7eb --- /dev/null +++ b/pkg/project/resource/resource_project_share_repository.go @@ -0,0 +1,261 @@ +package project + +import ( + "context" + "fmt" + "net/http" + "slices" + "strings" + + "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" + "github.com/jfrog/terraform-provider-shared/util" + utilfw "github.com/jfrog/terraform-provider-shared/util/fw" + validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" +) + +const shareWithTargetProject = "access/api/v1/projects/_/share/repositories/{repo_key}/{target_project_key}" + +func NewProjectShareRepositoryResource() resource.Resource { + return &ProjectShareRepositoryResource{ + TypeName: "project_share_repository", + } +} + +type ProjectShareRepositoryResource struct { + ProviderData util.ProviderMetadata + TypeName string +} + +type ProjectShareRepositoryResourceModel struct { + RepoKey types.String `tfsdk:"repo_key"` + TargetProjectKey types.String `tfsdk:"target_project_key"` +} + +func (r *ProjectShareRepositoryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = r.TypeName +} + +func (r *ProjectShareRepositoryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "repo_key": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + validatorfw_string.RepoKey(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "The key of the repository.", + }, + "target_project_key": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + validatorfw_string.ProjectKey(), + }, + Description: "The project key to which the repository should be shared with.", + }, + }, + Description: "Share a local or remote repository with a list of projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role.\n\n" + + "->Only available for Artifactory 7.90.1 or later.", + } +} + +func (r *ProjectShareRepositoryResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + r.ProviderData = req.ProviderData.(util.ProviderMetadata) + + supported, err := util.CheckVersion(r.ProviderData.ArtifactoryVersion, "7.90.1") + if err != nil { + resp.Diagnostics.AddError( + "Failed to check Artifactory version", + err.Error(), + ) + return + } + + if !supported { + resp.Diagnostics.AddError( + "Unsupported Artifactory version", + fmt.Sprintf("This resource is supported by Artifactory version 7.90.1 or later. Current version: %s", r.ProviderData.ArtifactoryVersion), + ) + return + } +} + +func (r *ProjectShareRepositoryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan ProjectShareRepositoryResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var projectError ProjectErrorsResponse + + response, err := r.ProviderData.Client.R(). + SetPathParams(map[string]string{ + "repo_key": plan.RepoKey.ValueString(), + "target_project_key": plan.TargetProjectKey.ValueString(), + }). + SetError(&projectError). + Put(shareWithTargetProject) + + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } + + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, projectError.String()) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ProjectShareRepositoryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var state ProjectShareRepositoryResourceModel + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + repoKey := state.RepoKey.ValueString() + + var status ProjectRepositoryStatusAPIModel + var projectError ProjectErrorsResponse + response, err := r.ProviderData.Client.R(). + SetPathParam("repo_key", repoKey). + SetResult(&status). + SetError(&projectError). + Get(ProjectRepositoryStatusEndpoint) + + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddWarning( + "repo not found", + repoKey, + ) + resp.State.RemoveResource(ctx) + return + } + + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, projectError.String()) + return + } + + if !slices.Contains(status.SharedWithProjects, state.TargetProjectKey.ValueString()) { + state.TargetProjectKey = types.StringNull() + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *ProjectShareRepositoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan ProjectShareRepositoryResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var projectError ProjectErrorsResponse + + response, err := r.ProviderData.Client.R(). + SetPathParams(map[string]string{ + "repo_key": plan.RepoKey.ValueString(), + "target_project_key": plan.TargetProjectKey.ValueString(), + }). + SetError(&projectError). + Put(shareWithTargetProject) + + if err != nil { + utilfw.UnableToUpdateResourceError(resp, err.Error()) + return + } + + if response.IsError() { + utilfw.UnableToUpdateResourceError(resp, projectError.String()) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ProjectShareRepositoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var state ProjectShareRepositoryResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var projectError ProjectErrorsResponse + + response, err := r.ProviderData.Client.R(). + SetPathParams(map[string]string{ + "repo_key": state.RepoKey.ValueString(), + "target_project_key": state.TargetProjectKey.ValueString(), + }). + SetError(&projectError). + Delete(shareWithTargetProject) + + if err != nil { + utilfw.UnableToDeleteResourceError(resp, err.Error()) + return + } + + if response.IsError() { + utilfw.UnableToDeleteResourceError(resp, projectError.String()) + return + } + + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} + +// ImportState imports the resource into the Terraform state. +func (r *ProjectShareRepositoryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parts := strings.SplitN(req.ID, ":", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + "Expected repo_key:target_project_key", + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("repo_key"), parts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("target_project_key"), parts[1])...) +} diff --git a/pkg/project/resource/resource_project_share_repository_test.go b/pkg/project/resource/resource_project_share_repository_test.go new file mode 100644 index 00000000..a000845a --- /dev/null +++ b/pkg/project/resource/resource_project_share_repository_test.go @@ -0,0 +1,144 @@ +package project_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + acctest "github.com/jfrog/terraform-provider-project/pkg/project/acctest" + "github.com/jfrog/terraform-provider-shared/testutil" + "github.com/jfrog/terraform-provider-shared/util" +) + +func TestAccProjectShareRepository_full(t *testing.T) { + client := acctest.GetTestResty(t) + version, err := util.GetArtifactoryVersion(client) + if err != nil { + t.Fatal(err) + } + valid, err := util.CheckVersion(version, "7.90.1") + if err != nil { + t.Fatal(err) + } + if !valid { + t.Skipf("Artifactory version %s is earlier than 7.90.1", version) + } + + projectKey1 := strings.ToLower(acctest.RandSeq(10)) + projectKey2 := strings.ToLower(acctest.RandSeq(10)) + projectName1 := fmt.Sprintf("tftestprojects%s", projectKey1) + projectName2 := fmt.Sprintf("tftestprojects%s", projectKey2) + + repoKey := fmt.Sprintf("repo%d", testutil.RandomInt()) + + _, fqrn, resourceName := testutil.MkNames("test-project-share-repo", "project_share_repository") + + params := map[string]string{ + "project_name_1": projectName1, + "project_key_1": projectKey1, + "repo_key": repoKey, + "resource_name": resourceName, + } + + temp := ` + resource "artifactory_local_generic_repository" "{{ .repo_key }}" { + key = "{{ .repo_key }}" + + lifecycle { + ignore_changes = ["project_key"] + } + } + + resource "project" "{{ .project_name_1 }}" { + key = "{{ .project_key_1 }}" + display_name = "{{ .project_name_1 }}" + description = "test description" + admin_privileges { + manage_members = true + manage_resources = true + index_resources = true + } + max_storage_in_gibibytes = 1 + block_deployments_on_limit = true + email_notification = false + } + + resource "project_share_repository" "{{ .resource_name }}" { + repo_key = artifactory_local_generic_repository.{{ .repo_key }}.key + target_project_key = project.{{ .project_name_1 }}.key + } + ` + + config := util.ExecuteTemplate("TestAccProjectShareRepository", temp, params) + + updatedTemp := ` + resource "artifactory_local_generic_repository" "{{ .repo_key }}" { + key = "{{ .repo_key }}" + + lifecycle { + ignore_changes = ["project_key"] + } + } + + resource "project" "{{ .project_name_2 }}" { + key = "{{ .project_key_2 }}" + display_name = "{{ .project_name_2 }}" + description = "test description" + admin_privileges { + manage_members = true + manage_resources = true + index_resources = true + } + max_storage_in_gibibytes = 1 + block_deployments_on_limit = true + email_notification = false + } + + resource "project_share_repository" "{{ .resource_name }}" { + repo_key = artifactory_local_generic_repository.{{ .repo_key }}.key + target_project_key = project.{{ .project_name_2 }}.key + } + ` + updateParams := map[string]string{ + "project_name_2": projectName2, + "project_key_2": projectKey2, + "repo_key": params["repo_key"], + "resource_name": resourceName, + } + + configUpdated := util.ExecuteTemplate("TestAccProjectShareRepository", updatedTemp, updateParams) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + }, + }, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "repo_key", params["repo_key"]), + resource.TestCheckResourceAttr(fqrn, "target_project_key", params["project_key_1"]), + ), + }, + { + Config: configUpdated, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "repo_key", updateParams["repo_key"]), + resource.TestCheckResourceAttr(fqrn, "target_project_key", updateParams["project_key_2"]), + ), + }, + { + ResourceName: fqrn, + ImportStateId: fmt.Sprintf("%s:%s", updateParams["repo_key"], updateParams["project_key_2"]), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "repo_key", + }, + }, + }) +} diff --git a/pkg/project/resource/resource_project_share_repository_with_all.go b/pkg/project/resource/resource_project_share_repository_with_all.go new file mode 100644 index 00000000..c7a84240 --- /dev/null +++ b/pkg/project/resource/resource_project_share_repository_with_all.go @@ -0,0 +1,208 @@ +package project + +import ( + "context" + "fmt" + "net/http" + + "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" + "github.com/jfrog/terraform-provider-shared/util" + utilfw "github.com/jfrog/terraform-provider-shared/util/fw" + validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" +) + +const shareWithAllProjectsEndpoint = "access/api/v1/projects/_/share/repositories/{repo_key}" + +func NewProjectShareRepositoryWithAllResource() resource.Resource { + return &ProjectShareRepositoryWithAllResource{ + TypeName: "project_share_repository_with_all", + } +} + +type ProjectShareRepositoryWithAllResource struct { + ProviderData util.ProviderMetadata + TypeName string +} + +type ProjectShareRepositoryWithAllResourceModel struct { + RepoKey types.String `tfsdk:"repo_key"` +} + +func (r *ProjectShareRepositoryWithAllResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = r.TypeName +} + +func (r *ProjectShareRepositoryWithAllResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "repo_key": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + validatorfw_string.RepoKey(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "The key of the repository.", + }, + }, + Description: "Share a local or remote repository with all projects. Project Members of the target project are granted actions to the shared repository according to their Roles and Role actions assigned in the target Project. Requires a user assigned with the 'Administer the Platform' role.\n\n" + + "->Only available for Artifactory 7.90.1 or later.", + } +} + +func (r *ProjectShareRepositoryWithAllResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + r.ProviderData = req.ProviderData.(util.ProviderMetadata) + + supported, err := util.CheckVersion(r.ProviderData.ArtifactoryVersion, "7.90.1") + if err != nil { + resp.Diagnostics.AddError( + "Failed to check Artifactory version", + err.Error(), + ) + return + } + + if !supported { + resp.Diagnostics.AddError( + "Unsupported Artifactory version", + fmt.Sprintf("This resource is supported by Artifactory version 7.90.1 or later. Current version: %s", r.ProviderData.ArtifactoryVersion), + ) + return + } +} + +func (r *ProjectShareRepositoryWithAllResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan ProjectShareRepositoryWithAllResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var projectError ProjectErrorsResponse + + response, err := r.ProviderData.Client.R(). + SetPathParam("repo_key", plan.RepoKey.ValueString()). + SetError(&projectError). + Put(shareWithAllProjectsEndpoint) + + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + } + + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, projectError.String()) + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ProjectShareRepositoryWithAllResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var state ProjectShareRepositoryWithAllResourceModel + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + repoKey := state.RepoKey.ValueString() + + var status ProjectRepositoryStatusAPIModel + var projectError ProjectErrorsResponse + response, err := r.ProviderData.Client.R(). + SetPathParam("repo_key", repoKey). + SetResult(&status). + SetError(&projectError). + Get("access/api/v1/projects/_/repositories/{repo_key}") + + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddWarning( + "repo not found", + repoKey, + ) + resp.State.RemoveResource(ctx) + return + } + + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, projectError.String()) + return + } + + if !status.SharedWithAllProjects { + resp.Diagnostics.AddWarning( + "repo is not shared with all projects", + repoKey, + ) + resp.State.RemoveResource(ctx) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *ProjectShareRepositoryWithAllResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddWarning( + "Update not supported", + "Repository assignment to project cannnot be updated.", + ) +} + +func (r *ProjectShareRepositoryWithAllResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var state ProjectShareRepositoryWithAllResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var projectError ProjectErrorsResponse + + response, err := r.ProviderData.Client.R(). + SetPathParam("repo_key", state.RepoKey.ValueString()). + SetError(&projectError). + Delete(shareWithAllProjectsEndpoint) + + if err != nil { + utilfw.UnableToDeleteResourceError(resp, err.Error()) + } + + if response.IsError() { + utilfw.UnableToDeleteResourceError(resp, projectError.String()) + } + + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} + +// ImportState imports the resource into the Terraform state. +func (r *ProjectShareRepositoryWithAllResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("repo_key"), req, resp) +} diff --git a/pkg/project/resource/resource_project_share_repository_with_all_test.go b/pkg/project/resource/resource_project_share_repository_with_all_test.go new file mode 100644 index 00000000..465017b9 --- /dev/null +++ b/pkg/project/resource/resource_project_share_repository_with_all_test.go @@ -0,0 +1,101 @@ +package project_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + acctest "github.com/jfrog/terraform-provider-project/pkg/project/acctest" + "github.com/jfrog/terraform-provider-shared/testutil" + "github.com/jfrog/terraform-provider-shared/util" +) + +func TestAccProjectShareWithAllRepository_full(t *testing.T) { + client := acctest.GetTestResty(t) + version, err := util.GetArtifactoryVersion(client) + if err != nil { + t.Fatal(err) + } + valid, err := util.CheckVersion(version, "7.90.1") + if err != nil { + t.Fatal(err) + } + if !valid { + t.Skipf("Artifactory version %s is earlier than 7.90.1", version) + } + + projectKey := strings.ToLower(acctest.RandSeq(10)) + projectName := fmt.Sprintf("tftestprojects%s", projectKey) + + repoKey := fmt.Sprintf("repo%d", testutil.RandomInt()) + + _, fqrn, resourceName := testutil.MkNames("test-project-share-repo", "project_share_repository_with_all") + + params := map[string]interface{}{ + "project_name": projectName, + "project_key": projectKey, + "repo_key": repoKey, + "resource_name": resourceName, + "share_with_all": "true", + } + + temp := ` + resource "artifactory_local_generic_repository" "{{ .repo_key }}" { + key = "{{ .repo_key }}" + + lifecycle { + ignore_changes = ["project_key"] + } + } + + resource "project" "{{ .project_name }}" { + key = "{{ .project_key }}" + display_name = "{{ .project_name }}" + description = "test description" + admin_privileges { + manage_members = true + manage_resources = true + index_resources = true + } + max_storage_in_gibibytes = 1 + block_deployments_on_limit = true + email_notification = false + } + + resource "project_share_repository_with_all" "{{ .resource_name }}" { + repo_key = artifactory_local_generic_repository.{{ .repo_key }}.key + + depends_on = [ + project.{{ .project_name }} + ] + } + ` + + config := util.ExecuteTemplate("TestAccProjectShareRepository", temp, params) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + }, + }, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "repo_key", params["repo_key"].(string)), + ), + }, + { + ResourceName: fqrn, + ImportStateId: params["repo_key"].(string), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "repo_key", + }, + }, + }) +} diff --git a/pkg/project/resource/resource_project_test.go b/pkg/project/resource/resource_project_test.go index 3c24ec64..a1ca28c8 100644 --- a/pkg/project/resource/resource_project_test.go +++ b/pkg/project/resource/resource_project_test.go @@ -19,12 +19,12 @@ func TestAccProject_UpgradeFromSDKv2(t *testing.T) { name := fmt.Sprintf("tftestprojects%s", acctest.RandSeq(10)) resourceName := fmt.Sprintf("project.%s", name) - username1 := "user1" + username1 := fmt.Sprintf("user1%s", strings.ToLower(acctest.RandSeq(5))) email1 := username1 + "@tempurl.org" - username2 := "user2" + username2 := fmt.Sprintf("user2%s", strings.ToLower(acctest.RandSeq(5))) email2 := username2 + "@tempurl.org" - group1 := "group1" - group2 := "group2" + group1 := fmt.Sprintf("group1%s", strings.ToLower(acctest.RandSeq(5))) + group2 := fmt.Sprintf("group2%s", strings.ToLower(acctest.RandSeq(5))) repo1 := fmt.Sprintf("repo%s", strings.ToLower(acctest.RandSeq(6))) repo2 := fmt.Sprintf("repo%s", strings.ToLower(acctest.RandSeq(6))) @@ -158,8 +158,7 @@ func TestAccProject_UpgradeFromSDKv2(t *testing.T) { Source: "jfrog/project", }, "artifactory": { - Source: "jfrog/artifactory", - VersionConstraint: "10.3.3", + Source: "jfrog/artifactory", }, }, Config: config, @@ -199,8 +198,7 @@ func TestAccProject_UpgradeFromSDKv2(t *testing.T) { { ExternalProviders: map[string]resource.ExternalProvider{ "artifactory": { - Source: "jfrog/artifactory", - VersionConstraint: "10.3.3", + Source: "jfrog/artifactory", }, }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, @@ -453,12 +451,12 @@ func TestAccProject_full(t *testing.T) { name := fmt.Sprintf("tftestprojects%s", acctest.RandSeq(10)) resourceName := fmt.Sprintf("project.%s", name) - username1 := "user1" + username1 := fmt.Sprintf("user1%s", strings.ToLower(acctest.RandSeq(5))) email1 := username1 + "@tempurl.org" - username2 := "user2" + username2 := fmt.Sprintf("user2%s", strings.ToLower(acctest.RandSeq(5))) email2 := username2 + "@tempurl.org" - group1 := "group1" - group2 := "group2" + group1 := fmt.Sprintf("group1%s", strings.ToLower(acctest.RandSeq(5))) + group2 := fmt.Sprintf("group2%s", strings.ToLower(acctest.RandSeq(5))) repo1 := fmt.Sprintf("repo%s", strings.ToLower(acctest.RandSeq(6))) repo2 := fmt.Sprintf("repo%s", strings.ToLower(acctest.RandSeq(6))) @@ -609,8 +607,7 @@ func TestAccProject_full(t *testing.T) { ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, ExternalProviders: map[string]resource.ExternalProvider{ "artifactory": { - Source: "jfrog/artifactory", - VersionConstraint: "10.3.3", + Source: "jfrog/artifactory", }, }, Steps: []resource.TestStep{ diff --git a/pkg/project/resource/resource_project_user.go b/pkg/project/resource/resource_project_user.go index fc211fd4..ea1c230b 100644 --- a/pkg/project/resource/resource_project_user.go +++ b/pkg/project/resource/resource_project_user.go @@ -24,7 +24,9 @@ import ( const ProjectUsersUrl = "access/api/v1/projects/{projectKey}/users/{name}" func NewProjectUserResource() resource.Resource { - return &ProjectUserResource{} + return &ProjectUserResource{ + TypeName: "project_user", + } } type ProjectUserResource struct { @@ -46,8 +48,7 @@ type ProjectUserAPIModel struct { } func (r *ProjectUserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_user" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } func (r *ProjectUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/pkg/project/resource/resource_project_user_test.go b/pkg/project/resource/resource_project_user_test.go index a8bc932d..24922500 100644 --- a/pkg/project/resource/resource_project_user_test.go +++ b/pkg/project/resource/resource_project_user_test.go @@ -169,8 +169,7 @@ func TestAccProjectUser_full(t *testing.T) { ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, ExternalProviders: map[string]resource.ExternalProvider{ "artifactory": { - Source: "jfrog/artifactory", - VersionConstraint: "10.3.3", + Source: "jfrog/artifactory", }, }, Steps: []resource.TestStep{ diff --git a/pkg/project/resource/util.go b/pkg/project/resource/util.go index 7d6eb3d7..c84dee67 100644 --- a/pkg/project/resource/util.go +++ b/pkg/project/resource/util.go @@ -43,3 +43,14 @@ func (r ProjectErrorsResponse) String() string { }, "") return errs } + +const ProjectRepositoryStatusEndpoint = "access/api/v1/projects/_/repositories/{repo_key}" + +type ProjectRepositoryStatusAPIModel struct { + ResourceName string `json:"resource_name"` + Environments []string `json:"environments"` + SharedWithProjects []string `json:"shared_with_projects"` + SharedWithAllProjects bool `json:"shared_with_all_projects"` + SharedReadOnly bool `json:"shared_read_only"` + AssignedTo string `json:"assigned_to"` +} From 3c35fdbdb30beb4cbcda5a339498fd0f0eee15f0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 23 Jul 2024 12:27:30 -0700 Subject: [PATCH 6/8] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12e87bc4..291f2efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 1.7.0 (July 24, 2024) + +NOTES: + +* provider: `check_license` attribute is deprecated and provider no longer checks Artifactory license during initialization. It will be removed in the next major version release. + +FEATURES: + +* **New Resource:** `project_share_repository` - New resource to share repository with a project. +* **New Resource:** `project_share_repository_with_all` - New resource to share repository with all projects. + +Issue: [#80](https://github.com/jfrog/terraform-provider-project/issues/80) +PR: [#147](https://github.com/jfrog/terraform-provider-project/pull/147) + ## 1.6.2 (June 4, 2024). Tested on Artifactory 7.84.14 with Terraform and OpenTofu 1.7.2 IMPROVEMENTS: From 0c72edbc076715586e5a54a19f2df2556edb1a24 Mon Sep 17 00:00:00 2001 From: JFrog CI Date: Tue, 23 Jul 2024 19:33:05 +0000 Subject: [PATCH 7/8] JFrog Pipelines - Add Artifactory version to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 291f2efe..fe496222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.7.0 (July 24, 2024) +## 1.7.0 (July 24, 2024). Tested on Artifactory 7.84.17 with Terraform 1.9.2 and OpenTofu 1.7.3 NOTES: From 0bb3ac3334647da14c39cfa310c2f11ed544bdaf Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 23 Jul 2024 12:56:20 -0700 Subject: [PATCH 8/8] Disable update for 'project_share_repository' --- .../resource_project_share_repository.go | 39 ++++--------------- ...ource_project_share_repository_with_all.go | 2 +- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/pkg/project/resource/resource_project_share_repository.go b/pkg/project/resource/resource_project_share_repository.go index a989c7eb..f8639c0f 100644 --- a/pkg/project/resource/resource_project_share_repository.go +++ b/pkg/project/resource/resource_project_share_repository.go @@ -59,6 +59,9 @@ func (r *ProjectShareRepositoryResource) Schema(ctx context.Context, req resourc Validators: []validator.String{ validatorfw_string.ProjectKey(), }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "The project key to which the repository should be shared with.", }, }, @@ -176,38 +179,10 @@ func (r *ProjectShareRepositoryResource) Read(ctx context.Context, req resource. } func (r *ProjectShareRepositoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - - var plan ProjectShareRepositoryResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - var projectError ProjectErrorsResponse - - response, err := r.ProviderData.Client.R(). - SetPathParams(map[string]string{ - "repo_key": plan.RepoKey.ValueString(), - "target_project_key": plan.TargetProjectKey.ValueString(), - }). - SetError(&projectError). - Put(shareWithTargetProject) - - if err != nil { - utilfw.UnableToUpdateResourceError(resp, err.Error()) - return - } - - if response.IsError() { - utilfw.UnableToUpdateResourceError(resp, projectError.String()) - return - } - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + resp.Diagnostics.AddWarning( + "Update not supported", + "Repository sharing with project cannnot be updated.", + ) } func (r *ProjectShareRepositoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { diff --git a/pkg/project/resource/resource_project_share_repository_with_all.go b/pkg/project/resource/resource_project_share_repository_with_all.go index c7a84240..f6c7f15b 100644 --- a/pkg/project/resource/resource_project_share_repository_with_all.go +++ b/pkg/project/resource/resource_project_share_repository_with_all.go @@ -168,7 +168,7 @@ func (r *ProjectShareRepositoryWithAllResource) Read(ctx context.Context, req re func (r *ProjectShareRepositoryWithAllResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { resp.Diagnostics.AddWarning( "Update not supported", - "Repository assignment to project cannnot be updated.", + "Repository sharing with all projects cannnot be updated.", ) }