From df42725e8e4498a67789601d93b7a2d6f65e354d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 2 Oct 2024 20:21:22 +0400 Subject: [PATCH 1/7] feat: gcp integration support --- .../integration_gcp_secret_manager.md | 42 ++ internal/client/integrations.go | 64 +++ internal/client/integrations_auth.go | 53 +++ internal/client/model.go | 113 ++++++ internal/provider/provider.go | 1 + .../integration_gcp_secret_manager.go | 380 ++++++++++++++++++ 6 files changed, 653 insertions(+) create mode 100644 docs/resources/integration_gcp_secret_manager.md create mode 100644 internal/client/integrations.go create mode 100644 internal/client/integrations_auth.go create mode 100644 internal/provider/resource/integration_gcp_secret_manager.go diff --git a/docs/resources/integration_gcp_secret_manager.md b/docs/resources/integration_gcp_secret_manager.md new file mode 100644 index 0000000..2affebf --- /dev/null +++ b/docs/resources/integration_gcp_secret_manager.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "infisical_integration_gcp_secret_manager Resource - terraform-provider-infisical" +subcategory: "" +description: |- + Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source +--- + +# infisical_integration_gcp_secret_manager (Resource) + +Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source + + + + +## Schema + +### Required + +- `environment` (String) The slug of the environment to sync to GCP Secret Manager (prod, dev, staging, etc). +- `gcp_project_id` (String) The slug of the GCP project. +- `project_id` (String) The ID of your Infisical project. +- `secret_path` (String) The path to the secret in GCP Secret Manager. +- `service_account_json` (String, Sensitive) Service account json for the GCP project. + +### Optional + +- `options` (Attributes) Integration options (see [below for nested schema](#nestedatt--options)) + +### Read-Only + +- `env_id` (String) The ID of the environment, used internally by Infisical. +- `integration_auth_id` (String) The ID of the integration auth, used internally by Infisical. +- `integration_id` (String) The ID of the integration, used internally by Infisical. + + +### Nested Schema for `options` + +Optional: + +- `secret_prefix` (String) The prefix to add to the secret name in GCP Secret Manager. +- `secret_suffix` (String) The suffix to add to the secret name in GCP Secret Manager. diff --git a/internal/client/integrations.go b/internal/client/integrations.go new file mode 100644 index 0000000..f1f0efd --- /dev/null +++ b/internal/client/integrations.go @@ -0,0 +1,64 @@ +package infisicalclient + +import ( + "fmt" +) + +func (client Client) CallCreateIntegration(request CreateIntegrationRequest) (CreateIntegrationResponse, error) { + var body CreateIntegrationResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Post("api/v1/integration") + + if err != nil { + return CreateIntegrationResponse{}, fmt.Errorf("CallCreateIntegration: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return CreateIntegrationResponse{}, fmt.Errorf("CallCreateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil +} + +func (client Client) CallGetIntegration(request GetIntegrationRequest) (GetIntegrationResponse, error) { + var body GetIntegrationResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + Get(fmt.Sprintf("api/v1/integration/%s", request.ID)) + + if err != nil { + return GetIntegrationResponse{}, fmt.Errorf("CallGetIntegration: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return GetIntegrationResponse{}, fmt.Errorf("CallGetIntegration: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil +} + +func (client Client) CallUpdateIntegration(request UpdateIntegrationRequest) (UpdateIntegrationResponse, error) { + var body UpdateIntegrationResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Patch(fmt.Sprintf("api/v1/integration/%s", request.ID)) + + if err != nil { + return UpdateIntegrationResponse{}, fmt.Errorf("CallUpdateIntegration: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return UpdateIntegrationResponse{}, fmt.Errorf("CallUpdateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil +} diff --git a/internal/client/integrations_auth.go b/internal/client/integrations_auth.go new file mode 100644 index 0000000..65602ff --- /dev/null +++ b/internal/client/integrations_auth.go @@ -0,0 +1,53 @@ +package infisicalclient + +import ( + "fmt" +) + +// enum containing the possible values for the `type` field in the CreateIntegrationAuthRequest +type IntegrationAuthType string + +const ( + IntegrationAuthTypeGcpSecretManager IntegrationAuthType = "gcp-secret-manager" +) + +func (client Client) CallCreateIntegrationAuth(request CreateIntegrationAuthRequest) (CreateIntegrationAuthResponse, error) { + var body CreateIntegrationAuthResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Post("api/v1/integration-auth/access-token") + + if err != nil { + return CreateIntegrationAuthResponse{}, fmt.Errorf("CallCreateIntegrationAuth: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return CreateIntegrationAuthResponse{}, fmt.Errorf("CallCreateIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil +} + +// Deleting integration auth triggers a cascade effect, that will also delete the associated integration. +func (client Client) CallDeleteIntegrationAuth(request DeleteIntegrationAuthRequest) (DeleteIntegrationAuthResponse, error) { + var body DeleteIntegrationAuthResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + Delete(fmt.Sprintf("api/v1/integration-auth/%s", request.ID)) + + if err != nil { + return DeleteIntegrationAuthResponse{}, fmt.Errorf("CallDeleteIntegrationAuth: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return DeleteIntegrationAuthResponse{}, fmt.Errorf("CallDeleteIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil + +} diff --git a/internal/client/model.go b/internal/client/model.go index 448dddd..7e1fb9d 100644 --- a/internal/client/model.go +++ b/internal/client/model.go @@ -1402,3 +1402,116 @@ type RevokeIdentityKubernetesAuthRequest struct { type RevokeIdentityKubernetesAuthResponse struct { IdentityKubernetesAuth IdentityKubernetesAuth `json:"identityKubernetesAuth"` } + +type CreateIntegrationAuthRequest struct { + RefreshToken string `json:"refreshToken"` + ProjectID string `json:"workspaceId"` + Integration IntegrationAuthType `json:"integration"` +} + +type CreateIntegrationAuthResponse struct { + IntegrationAuth struct { + ID string `json:"id"` + } `json:"integrationAuth"` +} + +type DeleteIntegrationAuthRequest struct { + ID string `json:"id"` +} + +type DeleteIntegrationAuthResponse struct { + IntegrationAuth struct { + ID string `json:"id"` + } `json:"integrationAuth"` +} + +type IntegrationMetadata struct { + InitialSyncBehavior string `json:"initialSyncBehavior,omitempty"` + SecretPrefix string `json:"secretPrefix,omitempty"` + SecretSuffix string `json:"secretSuffix,omitempty"` + MappingBehavior string `json:"mappingBehavior,omitempty"` + ShouldAutoRedeploy bool `json:"shouldAutoRedeploy,omitempty"` + SecretGCPLabel []struct { + LabelName string `json:"labelName,omitempty"` + LabelValue string `json:"labelValue,omitempty"` + } `json:"secretGCPLabel,omitempty"` + SecretAWSTag []struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + } `json:"secretAWSTag,omitempty"` + + GithubVisibility string `json:"githubVisibility,omitempty"` + GithubVisibilityRepoIDs []string `json:"githubVisibilityRepoIds,omitempty"` + KMSKeyID string `json:"kmsKeyId,omitempty"` + ShouldDisableDelete bool `json:"shouldDisableDelete,omitempty"` + ShouldEnableDelete bool `json:"shouldEnableDelete,omitempty"` + ShouldMaskSecrets bool `json:"shouldMaskSecrets,omitempty"` + ShouldProtectSecrets bool `json:"shouldProtectSecrets,omitempty"` +} +type CreateIntegrationRequest struct { + IntegrationAuthID string `json:"integrationAuthId"` + App string `json:"app,omitempty"` + AppID string `json:"appId,omitempty"` + SecretPath string `json:"secretPath,omitempty"` + SourceEnvironment string `json:"sourceEnvironment,omitempty"` + TargetEnvironment string `json:"targetEnvironment,omitempty"` + TargetEnvironmentID string `json:"targetEnvironmentId,omitempty"` + TargetService string `json:"targetService,omitempty"` + TargetServiceID string `json:"targetServiceId,omitempty"` + Owner string `json:"owner,omitempty"` + URL string `json:"url,omitempty"` + Path string `json:"path,omitempty"` + Region string `json:"region,omitempty"` + Scope string `json:"scope,omitempty"` + + Metadata IntegrationMetadata `json:"metadata,omitempty"` +} + +type Integration struct { + ID string `json:"id"` + IsActive bool `json:"isActive"` + URL string `json:"url"` + App string `json:"app"` + AppID string `json:"appId"` + TargetEnvironment string `json:"targetEnvironment"` + TargetEnvironmentID string `json:"targetEnvironmentId"` + TargetService string `json:"targetService"` + TargetServiceID string `json:"targetServiceId"` + Owner string `json:"owner"` + Path string `json:"path"` + Region string `json:"region"` + Scope string `json:"scope"` + Integration string `json:"integration"` + Metadata IntegrationMetadata `json:"metadata,omitempty"` + IntegrationAuthID string `json:"integrationAuthId"` + EnvID string `json:"envId"` + SecretPath string `json:"secretPath"` +} + +type CreateIntegrationResponse struct { + Integration Integration `json:"integration"` +} + +type GetIntegrationRequest struct { + ID string +} + +type GetIntegrationResponse struct { + Integration Integration `json:"integration"` +} + +type UpdateIntegrationRequest struct { + ID string + App string `json:"app,omitempty"` + AppID string `json:"appId,omitempty"` + SecretPath string `json:"secretPath,omitempty"` + TargetEnvironment string `json:"targetEnvironment,omitempty"` + Owner string `json:"owner,omitempty"` + Environment string `json:"environment,omitempty"` + Metadata IntegrationMetadata `json:"metadata,omitempty"` + IsActive bool `json:"isActive"` +} + +type UpdateIntegrationResponse struct { + Integration Integration `json:"integration"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4f233c5..ca28292 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -250,5 +250,6 @@ func (p *infisicalProvider) Resources(_ context.Context) []func() resource.Resou infisicalResource.NewIdentityGcpAuthResource, infisicalResource.NewIdentityAzureAuthResource, infisicalResource.NewIdentityOidcAuthResource, + infisicalResource.NewIntegrationResource, } } diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go new file mode 100644 index 0000000..49513d6 --- /dev/null +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -0,0 +1,380 @@ +package resource + +import ( + "context" + "fmt" + infisical "terraform-provider-infisical/internal/client" + + "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &IntegrationGcpSecretManagerResource{} +) + +// NewProjectResource is a helper function to simplify the provider implementation. +func NewIntegrationResource() resource.Resource { + return &IntegrationGcpSecretManagerResource{} +} + +// ProjectUserResource is the resource implementation. +type IntegrationGcpSecretManagerResource struct { + client *infisical.Client +} + +// projectResourceSourceModel describes the data source data model. +type IntegrationSecretManagerResourceModel struct { + EnvironmentID types.String `tfsdk:"env_id"` + IntegrationAuthID types.String `tfsdk:"integration_auth_id"` + IntegrationID types.String `tfsdk:"integration_id"` + ServiceAccountJson types.String `tfsdk:"service_account_json"` + ProjectID types.String `tfsdk:"project_id"` + Environment types.String `tfsdk:"environment"` + SecretPath types.String `tfsdk:"secret_path"` + GCPProjectID types.String `tfsdk:"gcp_project_id"` + + Options types.Object `tfsdk:"options"` +} + +// Metadata returns the resource type name. +func (r *IntegrationGcpSecretManagerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_integration_gcp_secret_manager" +} + +// Schema defines the schema for the resource. +func (r *IntegrationGcpSecretManagerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source", + Attributes: map[string]schema.Attribute{ + "options": schema.SingleNestedAttribute{ + Description: "Integration options", + Optional: true, + Attributes: map[string]schema.Attribute{ + "secret_prefix": schema.StringAttribute{ + Description: "The prefix to add to the secret name in GCP Secret Manager.", + Optional: true, + }, + "secret_suffix": schema.StringAttribute{ + Description: "The suffix to add to the secret name in GCP Secret Manager.", + Optional: true, + }, + }, + }, + + "integration_auth_id": schema.StringAttribute{ + Computed: true, + Description: "The ID of the integration auth, used internally by Infisical.", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + + "env_id": schema.StringAttribute{ + Computed: true, + Description: "The ID of the environment, used internally by Infisical.", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + + "integration_id": schema.StringAttribute{ + Computed: true, + Description: "The ID of the integration, used internally by Infisical.", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + + "service_account_json": schema.StringAttribute{ + Sensitive: true, + Required: true, + Description: "Service account json for the GCP project.", + }, + + "project_id": schema.StringAttribute{ + Required: true, + Description: "The ID of your Infisical project.", + }, + + "environment": schema.StringAttribute{ + Required: true, + Description: "The slug of the environment to sync to GCP Secret Manager (prod, dev, staging, etc).", + }, + + "secret_path": schema.StringAttribute{ + Required: true, + Description: "The path to the secret in GCP Secret Manager.", + }, + + "gcp_project_id": schema.StringAttribute{ + Required: true, + Description: "The slug of the GCP project.", + }, + }, + } +} + +// Configure adds the provider configured client to the resource. +func (r *IntegrationGcpSecretManagerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*infisical.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *IntegrationGcpSecretManagerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + if !r.client.Config.IsMachineIdentityAuth { + resp.Diagnostics.AddError( + "Unable to create identity aws auth", + "Only Machine Identity authentication is supported for this operation", + ) + return + } + + // Retrieve values from plan + var plan IntegrationSecretManagerResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Create integration auth first + + auth, err := r.client.CallCreateIntegrationAuth(infisical.CreateIntegrationAuthRequest{ + RefreshToken: plan.ServiceAccountJson.ValueString(), + ProjectID: plan.ProjectID.ValueString(), + Integration: infisical.IntegrationAuthTypeGcpSecretManager, + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to create integration auth", + err.Error(), + ) + return + } + + metadata := infisical.IntegrationMetadata{} + + if !plan.Options.IsNull() && !plan.Options.IsUnknown() { + var options struct { + SecretPrefix types.String `tfsdk:"secret_prefix"` + SecretSuffix types.String `tfsdk:"secret_suffix"` + } + diags := plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if !options.SecretPrefix.IsNull() && !options.SecretPrefix.IsUnknown() { + metadata.SecretPrefix = options.SecretPrefix.ValueString() + } + if !options.SecretSuffix.IsNull() && !options.SecretSuffix.IsUnknown() { + metadata.SecretSuffix = options.SecretSuffix.ValueString() + } + } + + // Create the integration + integration, err := r.client.CallCreateIntegration(infisical.CreateIntegrationRequest{ + IntegrationAuthID: auth.IntegrationAuth.ID, + App: plan.GCPProjectID.ValueString(), + AppID: plan.GCPProjectID.ValueString(), + SecretPath: plan.SecretPath.ValueString(), + SourceEnvironment: plan.Environment.ValueString(), + Metadata: metadata, + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to create integration", + err.Error(), + ) + return + } + + // Set the state + + plan.IntegrationAuthID = types.StringValue(auth.IntegrationAuth.ID) + plan.IntegrationID = types.StringValue(integration.Integration.ID) + plan.EnvironmentID = types.StringValue(integration.Integration.EnvID) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *IntegrationGcpSecretManagerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + if !r.client.Config.IsMachineIdentityAuth { + resp.Diagnostics.AddError( + "Unable to read identity role", + "Only Machine Identity authentication is supported for this operation", + ) + return + } + + // Get current state + var state IntegrationSecretManagerResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + integration, err := r.client.CallGetIntegration(infisical.GetIntegrationRequest{ + ID: state.IntegrationID.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to get integration", + err.Error(), + ) + return + } + + state.GCPProjectID = types.StringValue(integration.Integration.AppID) + state.SecretPath = types.StringValue(integration.Integration.SecretPath) + state.EnvironmentID = types.StringValue(integration.Integration.EnvID) + state.IntegrationAuthID = types.StringValue(integration.Integration.IntegrationAuthID) + + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *IntegrationGcpSecretManagerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + + if !r.client.Config.IsMachineIdentityAuth { + resp.Diagnostics.AddError( + "Unable to update identity", + "Only Machine Identity authentication is supported for this operation", + ) + return + } + + // Retrieve values from plan + var plan IntegrationSecretManagerResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var state IntegrationSecretManagerResourceModel + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if plan.ProjectID != state.ProjectID { + resp.Diagnostics.AddError( + "Project ID cannot be updated", + "Project ID cannot be updated", + ) + return + } + + metadata := infisical.IntegrationMetadata{} + + if !plan.Options.IsNull() && !plan.Options.IsUnknown() { + var options struct { + SecretPrefix types.String `tfsdk:"secret_prefix"` + SecretSuffix types.String `tfsdk:"secret_suffix"` + } + diags := plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if !options.SecretPrefix.IsNull() && !options.SecretPrefix.IsUnknown() { + metadata.SecretPrefix = options.SecretPrefix.ValueString() + } + if !options.SecretSuffix.IsNull() && !options.SecretSuffix.IsUnknown() { + metadata.SecretSuffix = options.SecretSuffix.ValueString() + } + } + + updatedIntegration, err := r.client.CallUpdateIntegration(infisical.UpdateIntegrationRequest{ + IsActive: true, + ID: state.IntegrationID.ValueString(), + Environment: plan.Environment.ValueString(), + App: plan.GCPProjectID.ValueString(), + AppID: plan.GCPProjectID.ValueString(), + SecretPath: plan.SecretPath.ValueString(), + Metadata: metadata, + }) + + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration", + err.Error(), + ) + return + } + + plan.EnvironmentID = types.StringValue(updatedIntegration.Integration.EnvID) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *IntegrationGcpSecretManagerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + if !r.client.Config.IsMachineIdentityAuth { + resp.Diagnostics.AddError( + "Unable to delete GCP Secret Manager integration", + "Only Machine Identity authentication is supported for this operation", + ) + return + } + + var state IntegrationSecretManagerResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + _, err := r.client.CallDeleteIntegrationAuth(infisical.DeleteIntegrationAuthRequest{ + ID: state.IntegrationAuthID.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError( + "Error deleting GCP Secret Manager Integration", + "Couldn't delete GCP Secret Manager integration from your Infiscial project, unexpected error: "+err.Error(), + ) + return + } + +} From 4364ec3119299bc24ad21a0fb28346627e4d5a65 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 2 Oct 2024 20:25:35 +0400 Subject: [PATCH 2/7] fix lint --- internal/client/integrations_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/client/integrations_auth.go b/internal/client/integrations_auth.go index 65602ff..8913ae0 100644 --- a/internal/client/integrations_auth.go +++ b/internal/client/integrations_auth.go @@ -4,7 +4,7 @@ import ( "fmt" ) -// enum containing the possible values for the `type` field in the CreateIntegrationAuthRequest +// Enum containing the possible values for the `type` field in the CreateIntegrationAuthRequest. type IntegrationAuthType string const ( From 90413d27e2ebac88e1bd53a76f92f86ae3e20f56 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 3 Oct 2024 00:52:32 +0400 Subject: [PATCH 3/7] chore: requested changes --- .../integration_gcp_secret_manager.md | 8 +-- internal/client/integrations.go | 14 ++--- internal/client/integrations_auth.go | 12 ++-- internal/provider/provider.go | 2 +- .../integration_gcp_secret_manager.go | 59 +++++++++---------- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/docs/resources/integration_gcp_secret_manager.md b/docs/resources/integration_gcp_secret_manager.md index 2affebf..2d66845 100644 --- a/docs/resources/integration_gcp_secret_manager.md +++ b/docs/resources/integration_gcp_secret_manager.md @@ -3,12 +3,12 @@ page_title: "infisical_integration_gcp_secret_manager Resource - terraform-provider-infisical" subcategory: "" description: |- - Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source + Create GCP Secret Manager integration & save to Infisical. Only Machine Identity authentication is supported for this data source --- # infisical_integration_gcp_secret_manager (Resource) -Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source +Create GCP Secret Manager integration & save to Infisical. Only Machine Identity authentication is supported for this data source @@ -18,9 +18,9 @@ Create project users & save to Infisical. Only Machine Identity authentication i ### Required - `environment` (String) The slug of the environment to sync to GCP Secret Manager (prod, dev, staging, etc). -- `gcp_project_id` (String) The slug of the GCP project. +- `gcp_project_id` (String) The ID of the GCP project. - `project_id` (String) The ID of your Infisical project. -- `secret_path` (String) The path to the secret in GCP Secret Manager. +- `secret_path` (String) The secret path in Infisical to sync secrets from. - `service_account_json` (String, Sensitive) Service account json for the GCP project. ### Optional diff --git a/internal/client/integrations.go b/internal/client/integrations.go index f1f0efd..9e12929 100644 --- a/internal/client/integrations.go +++ b/internal/client/integrations.go @@ -4,7 +4,7 @@ import ( "fmt" ) -func (client Client) CallCreateIntegration(request CreateIntegrationRequest) (CreateIntegrationResponse, error) { +func (client Client) CreateIntegration(request CreateIntegrationRequest) (CreateIntegrationResponse, error) { var body CreateIntegrationResponse response, err := client.Config.HttpClient. R(). @@ -14,17 +14,17 @@ func (client Client) CallCreateIntegration(request CreateIntegrationRequest) (Cr Post("api/v1/integration") if err != nil { - return CreateIntegrationResponse{}, fmt.Errorf("CallCreateIntegration: Unable to complete api request [err=%s]", err) + return CreateIntegrationResponse{}, fmt.Errorf("CreateIntegration: Unable to complete api request [err=%s]", err) } if response.IsError() { - return CreateIntegrationResponse{}, fmt.Errorf("CallCreateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) + return CreateIntegrationResponse{}, fmt.Errorf("CreateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) } return body, nil } -func (client Client) CallGetIntegration(request GetIntegrationRequest) (GetIntegrationResponse, error) { +func (client Client) GetIntegration(request GetIntegrationRequest) (GetIntegrationResponse, error) { var body GetIntegrationResponse response, err := client.Config.HttpClient. R(). @@ -43,7 +43,7 @@ func (client Client) CallGetIntegration(request GetIntegrationRequest) (GetInteg return body, nil } -func (client Client) CallUpdateIntegration(request UpdateIntegrationRequest) (UpdateIntegrationResponse, error) { +func (client Client) UpdateIntegration(request UpdateIntegrationRequest) (UpdateIntegrationResponse, error) { var body UpdateIntegrationResponse response, err := client.Config.HttpClient. R(). @@ -53,11 +53,11 @@ func (client Client) CallUpdateIntegration(request UpdateIntegrationRequest) (Up Patch(fmt.Sprintf("api/v1/integration/%s", request.ID)) if err != nil { - return UpdateIntegrationResponse{}, fmt.Errorf("CallUpdateIntegration: Unable to complete api request [err=%s]", err) + return UpdateIntegrationResponse{}, fmt.Errorf("UpdateIntegration: Unable to complete api request [err=%s]", err) } if response.IsError() { - return UpdateIntegrationResponse{}, fmt.Errorf("CallUpdateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) + return UpdateIntegrationResponse{}, fmt.Errorf("UpdateIntegration: Unsuccessful response. [response=%s]", string(response.Body())) } return body, nil diff --git a/internal/client/integrations_auth.go b/internal/client/integrations_auth.go index 8913ae0..f33836e 100644 --- a/internal/client/integrations_auth.go +++ b/internal/client/integrations_auth.go @@ -11,7 +11,7 @@ const ( IntegrationAuthTypeGcpSecretManager IntegrationAuthType = "gcp-secret-manager" ) -func (client Client) CallCreateIntegrationAuth(request CreateIntegrationAuthRequest) (CreateIntegrationAuthResponse, error) { +func (client Client) CreateIntegrationAuth(request CreateIntegrationAuthRequest) (CreateIntegrationAuthResponse, error) { var body CreateIntegrationAuthResponse response, err := client.Config.HttpClient. R(). @@ -21,18 +21,18 @@ func (client Client) CallCreateIntegrationAuth(request CreateIntegrationAuthRequ Post("api/v1/integration-auth/access-token") if err != nil { - return CreateIntegrationAuthResponse{}, fmt.Errorf("CallCreateIntegrationAuth: Unable to complete api request [err=%s]", err) + return CreateIntegrationAuthResponse{}, fmt.Errorf("CreateIntegrationAuth: Unable to complete api request [err=%s]", err) } if response.IsError() { - return CreateIntegrationAuthResponse{}, fmt.Errorf("CallCreateIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) + return CreateIntegrationAuthResponse{}, fmt.Errorf("CreateIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) } return body, nil } // Deleting integration auth triggers a cascade effect, that will also delete the associated integration. -func (client Client) CallDeleteIntegrationAuth(request DeleteIntegrationAuthRequest) (DeleteIntegrationAuthResponse, error) { +func (client Client) DeleteIntegrationAuth(request DeleteIntegrationAuthRequest) (DeleteIntegrationAuthResponse, error) { var body DeleteIntegrationAuthResponse response, err := client.Config.HttpClient. R(). @@ -41,11 +41,11 @@ func (client Client) CallDeleteIntegrationAuth(request DeleteIntegrationAuthRequ Delete(fmt.Sprintf("api/v1/integration-auth/%s", request.ID)) if err != nil { - return DeleteIntegrationAuthResponse{}, fmt.Errorf("CallDeleteIntegrationAuth: Unable to complete api request [err=%s]", err) + return DeleteIntegrationAuthResponse{}, fmt.Errorf("DeleteIntegrationAuth: Unable to complete api request [err=%s]", err) } if response.IsError() { - return DeleteIntegrationAuthResponse{}, fmt.Errorf("CallDeleteIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) + return DeleteIntegrationAuthResponse{}, fmt.Errorf("DeleteIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) } return body, nil diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ca28292..8d0e373 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -250,6 +250,6 @@ func (p *infisicalProvider) Resources(_ context.Context) []func() resource.Resou infisicalResource.NewIdentityGcpAuthResource, infisicalResource.NewIdentityAzureAuthResource, infisicalResource.NewIdentityOidcAuthResource, - infisicalResource.NewIntegrationResource, + infisicalResource.NewIntegrationGcpSecretManagerResource, } } diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go index 49513d6..c33b006 100644 --- a/internal/provider/resource/integration_gcp_secret_manager.go +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -15,21 +15,21 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ resource.Resource = &IntegrationGcpSecretManagerResource{} + _ resource.Resource = &IntegrationGCPSecretManagerResource{} ) // NewProjectResource is a helper function to simplify the provider implementation. -func NewIntegrationResource() resource.Resource { - return &IntegrationGcpSecretManagerResource{} +func NewIntegrationGcpSecretManagerResource() resource.Resource { + return &IntegrationGCPSecretManagerResource{} } -// ProjectUserResource is the resource implementation. -type IntegrationGcpSecretManagerResource struct { +// IntegrationGcpSecretManager is the resource implementation. +type IntegrationGCPSecretManagerResource struct { client *infisical.Client } // projectResourceSourceModel describes the data source data model. -type IntegrationSecretManagerResourceModel struct { +type IntegrationGCPSecretManagerResourceModel struct { EnvironmentID types.String `tfsdk:"env_id"` IntegrationAuthID types.String `tfsdk:"integration_auth_id"` IntegrationID types.String `tfsdk:"integration_id"` @@ -43,14 +43,14 @@ type IntegrationSecretManagerResourceModel struct { } // Metadata returns the resource type name. -func (r *IntegrationGcpSecretManagerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *IntegrationGCPSecretManagerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_integration_gcp_secret_manager" } // Schema defines the schema for the resource. -func (r *IntegrationGcpSecretManagerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *IntegrationGCPSecretManagerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Create project users & save to Infisical. Only Machine Identity authentication is supported for this data source", + Description: "Create GCP Secret Manager integration & save to Infisical. Only Machine Identity authentication is supported for this data source", Attributes: map[string]schema.Attribute{ "options": schema.SingleNestedAttribute{ Description: "Integration options", @@ -103,19 +103,19 @@ func (r *IntegrationGcpSecretManagerResource) Schema(_ context.Context, _ resour "secret_path": schema.StringAttribute{ Required: true, - Description: "The path to the secret in GCP Secret Manager.", + Description: "The secret path in Infisical to sync secrets from.", }, "gcp_project_id": schema.StringAttribute{ Required: true, - Description: "The slug of the GCP project.", + Description: "The ID of the GCP project.", }, }, } } // Configure adds the provider configured client to the resource. -func (r *IntegrationGcpSecretManagerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *IntegrationGCPSecretManagerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -135,17 +135,17 @@ func (r *IntegrationGcpSecretManagerResource) Configure(_ context.Context, req r } // Create creates the resource and sets the initial Terraform state. -func (r *IntegrationGcpSecretManagerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *IntegrationGCPSecretManagerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { if !r.client.Config.IsMachineIdentityAuth { resp.Diagnostics.AddError( - "Unable to create identity aws auth", + "Unable to create integration", "Only Machine Identity authentication is supported for this operation", ) return } // Retrieve values from plan - var plan IntegrationSecretManagerResourceModel + var plan IntegrationGCPSecretManagerResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -153,8 +153,7 @@ func (r *IntegrationGcpSecretManagerResource) Create(ctx context.Context, req re } // Create integration auth first - - auth, err := r.client.CallCreateIntegrationAuth(infisical.CreateIntegrationAuthRequest{ + auth, err := r.client.CreateIntegrationAuth(infisical.CreateIntegrationAuthRequest{ RefreshToken: plan.ServiceAccountJson.ValueString(), ProjectID: plan.ProjectID.ValueString(), Integration: infisical.IntegrationAuthTypeGcpSecretManager, @@ -191,7 +190,7 @@ func (r *IntegrationGcpSecretManagerResource) Create(ctx context.Context, req re } // Create the integration - integration, err := r.client.CallCreateIntegration(infisical.CreateIntegrationRequest{ + integration, err := r.client.CreateIntegration(infisical.CreateIntegrationRequest{ IntegrationAuthID: auth.IntegrationAuth.ID, App: plan.GCPProjectID.ValueString(), AppID: plan.GCPProjectID.ValueString(), @@ -222,24 +221,24 @@ func (r *IntegrationGcpSecretManagerResource) Create(ctx context.Context, req re } // Read refreshes the Terraform state with the latest data. -func (r *IntegrationGcpSecretManagerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *IntegrationGCPSecretManagerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { if !r.client.Config.IsMachineIdentityAuth { resp.Diagnostics.AddError( - "Unable to read identity role", + "Unable to create integration", "Only Machine Identity authentication is supported for this operation", ) return } // Get current state - var state IntegrationSecretManagerResourceModel + var state IntegrationGCPSecretManagerResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - integration, err := r.client.CallGetIntegration(infisical.GetIntegrationRequest{ + integration, err := r.client.GetIntegration(infisical.GetIntegrationRequest{ ID: state.IntegrationID.ValueString(), }) @@ -265,25 +264,25 @@ func (r *IntegrationGcpSecretManagerResource) Read(ctx context.Context, req reso } // Update updates the resource and sets the updated Terraform state on success. -func (r *IntegrationGcpSecretManagerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *IntegrationGCPSecretManagerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { if !r.client.Config.IsMachineIdentityAuth { resp.Diagnostics.AddError( - "Unable to update identity", + "Unable to update integration", "Only Machine Identity authentication is supported for this operation", ) return } // Retrieve values from plan - var plan IntegrationSecretManagerResourceModel + var plan IntegrationGCPSecretManagerResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var state IntegrationSecretManagerResourceModel + var state IntegrationGCPSecretManagerResourceModel diags = req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -320,7 +319,7 @@ func (r *IntegrationGcpSecretManagerResource) Update(ctx context.Context, req re } } - updatedIntegration, err := r.client.CallUpdateIntegration(infisical.UpdateIntegrationRequest{ + updatedIntegration, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ IsActive: true, ID: state.IntegrationID.ValueString(), Environment: plan.Environment.ValueString(), @@ -349,7 +348,7 @@ func (r *IntegrationGcpSecretManagerResource) Update(ctx context.Context, req re } // Delete deletes the resource and removes the Terraform state on success. -func (r *IntegrationGcpSecretManagerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *IntegrationGCPSecretManagerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { if !r.client.Config.IsMachineIdentityAuth { resp.Diagnostics.AddError( "Unable to delete GCP Secret Manager integration", @@ -358,14 +357,14 @@ func (r *IntegrationGcpSecretManagerResource) Delete(ctx context.Context, req re return } - var state IntegrationSecretManagerResourceModel + var state IntegrationGCPSecretManagerResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - _, err := r.client.CallDeleteIntegrationAuth(infisical.DeleteIntegrationAuthRequest{ + _, err := r.client.DeleteIntegrationAuth(infisical.DeleteIntegrationAuthRequest{ ID: state.IntegrationAuthID.ValueString(), }) From fa80146aab08a024265e13de12fc2e138fa5ec52 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 3 Oct 2024 18:24:55 +0400 Subject: [PATCH 4/7] docs --- .../integration_gcp_secret_manager.md | 33 +++++++++++++++++++ .../resource.tf | 30 +++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 examples/resources/infisical_integration_gcp_secret_manager/resource.tf diff --git a/docs/resources/integration_gcp_secret_manager.md b/docs/resources/integration_gcp_secret_manager.md index 2d66845..39757a7 100644 --- a/docs/resources/integration_gcp_secret_manager.md +++ b/docs/resources/integration_gcp_secret_manager.md @@ -10,7 +10,40 @@ description: |- Create GCP Secret Manager integration & save to Infisical. Only Machine Identity authentication is supported for this data source +## Example Usage +```terraform +terraform { + required_providers { + infisical = { + # version = + source = "infisical/infisical" + } + } +} + +provider "infisical" { + host = "https://app.infisical.com" # Only required if using self hosted instance of Infisical, default is https://app.infisical.com + client_id = "" + client_secret = "" +} + +variable "service_account_json" { + type = string + description = "Google Cloud service account JSON key" +} + + + +resource "infisical_integration_gcp_secret_manager" "gcp-integration" { + project_id = "your-project-id" + service_account_json = var.service_account_json + environment = "dev" + secret_path = "/" + gcp_project_id = "gcp-project-id" + +} +``` ## Schema diff --git a/examples/resources/infisical_integration_gcp_secret_manager/resource.tf b/examples/resources/infisical_integration_gcp_secret_manager/resource.tf new file mode 100644 index 0000000..c868045 --- /dev/null +++ b/examples/resources/infisical_integration_gcp_secret_manager/resource.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + infisical = { + # version = + source = "infisical/infisical" + } + } +} + +provider "infisical" { + host = "https://app.infisical.com" # Only required if using self hosted instance of Infisical, default is https://app.infisical.com + client_id = "" + client_secret = "" +} + +variable "service_account_json" { + type = string + description = "Google Cloud service account JSON key" +} + + + +resource "infisical_integration_gcp_secret_manager" "gcp-integration" { + project_id = "your-project-id" + service_account_json = var.service_account_json + environment = "dev" + secret_path = "/" + gcp_project_id = "gcp-project-id" + +} \ No newline at end of file From 212a598ac6df2248d4bc04eedd315967c4594593 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 3 Oct 2024 18:32:28 +0400 Subject: [PATCH 5/7] fix: handle integration not found --- internal/client/integrations.go | 4 ++++ .../resource/integration_gcp_secret_manager.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/client/integrations.go b/internal/client/integrations.go index 9e12929..236c310 100644 --- a/internal/client/integrations.go +++ b/internal/client/integrations.go @@ -2,6 +2,7 @@ package infisicalclient import ( "fmt" + "net/http" ) func (client Client) CreateIntegration(request CreateIntegrationRequest) (CreateIntegrationResponse, error) { @@ -37,6 +38,9 @@ func (client Client) GetIntegration(request GetIntegrationRequest) (GetIntegrati } if response.IsError() { + if response.StatusCode() == http.StatusNotFound { + return GetIntegrationResponse{}, ErrNotFound + } return GetIntegrationResponse{}, fmt.Errorf("CallGetIntegration: Unsuccessful response. [response=%s]", string(response.Body())) } diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go index c33b006..d3dd709 100644 --- a/internal/provider/resource/integration_gcp_secret_manager.go +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -243,10 +243,14 @@ func (r *IntegrationGCPSecretManagerResource) Read(ctx context.Context, req reso }) if err != nil { - resp.Diagnostics.AddError( - "Unable to get integration", - err.Error(), - ) + if err == infisical.ErrNotFound { + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Unable to get integration", + err.Error(), + ) + } return } From eacdd528fc6efd7667cfcea186a2266d7aa8bad5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 3 Oct 2024 20:56:08 +0400 Subject: [PATCH 6/7] check for serverside option drifts --- internal/client/model.go | 4 +- .../integration_gcp_secret_manager.go | 79 ++++++++++++++----- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/internal/client/model.go b/internal/client/model.go index 8fa92a2..5e144cd 100644 --- a/internal/client/model.go +++ b/internal/client/model.go @@ -1433,8 +1433,8 @@ type DeleteIntegrationAuthResponse struct { type IntegrationMetadata struct { InitialSyncBehavior string `json:"initialSyncBehavior,omitempty"` - SecretPrefix string `json:"secretPrefix,omitempty"` - SecretSuffix string `json:"secretSuffix,omitempty"` + SecretPrefix string `json:"secretPrefix"` + SecretSuffix string `json:"secretSuffix"` MappingBehavior string `json:"mappingBehavior,omitempty"` ShouldAutoRedeploy bool `json:"shouldAutoRedeploy,omitempty"` SecretGCPLabel []struct { diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go index d3dd709..d2fceca 100644 --- a/internal/provider/resource/integration_gcp_secret_manager.go +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -5,6 +5,7 @@ import ( "fmt" infisical "terraform-provider-infisical/internal/client" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -254,6 +255,56 @@ func (r *IntegrationGCPSecretManagerResource) Read(ctx context.Context, req reso return } + var planOptions struct { + SecretPrefix types.String `tfsdk:"secret_prefix"` + SecretSuffix types.String `tfsdk:"secret_suffix"` + } + + if !state.Options.IsNull() { + diags := state.Options.As(ctx, &planOptions, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + updateNeeded := false + + if integration.Integration.Metadata.SecretPrefix != planOptions.SecretPrefix.ValueString() { + planOptions.SecretPrefix = types.StringValue(integration.Integration.Metadata.SecretPrefix) + updateNeeded = true + } + + if planOptions.SecretSuffix.ValueString() != integration.Integration.Metadata.SecretSuffix { + planOptions.SecretSuffix = types.StringValue(integration.Integration.Metadata.SecretSuffix) + updateNeeded = true + } + + if updateNeeded { + // Create a map of the updated options + optionsMap := map[string]attr.Value{ + "secret_prefix": planOptions.SecretPrefix, + "secret_suffix": planOptions.SecretSuffix, + } + + // Create a new types.Object with the updated options + newOptions, diags := types.ObjectValue( + map[string]attr.Type{ + "secret_prefix": types.StringType, + "secret_suffix": types.StringType, + }, + optionsMap, + ) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set the new options in the state + state.Options = newOptions + } + + // Set the state.Options. state.GCPProjectID = types.StringValue(integration.Integration.AppID) state.SecretPath = types.StringValue(integration.Integration.SecretPath) state.EnvironmentID = types.StringValue(integration.Integration.EnvID) @@ -301,27 +352,15 @@ func (r *IntegrationGCPSecretManagerResource) Update(ctx context.Context, req re return } - metadata := infisical.IntegrationMetadata{} - - if !plan.Options.IsNull() && !plan.Options.IsUnknown() { - var options struct { - SecretPrefix types.String `tfsdk:"secret_prefix"` - SecretSuffix types.String `tfsdk:"secret_suffix"` - } - diags := plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) - - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - if !options.SecretPrefix.IsNull() && !options.SecretPrefix.IsUnknown() { - metadata.SecretPrefix = options.SecretPrefix.ValueString() - } - if !options.SecretSuffix.IsNull() && !options.SecretSuffix.IsUnknown() { - metadata.SecretSuffix = options.SecretSuffix.ValueString() - } + var options struct { + SecretPrefix types.String `tfsdk:"secret_prefix"` + SecretSuffix types.String `tfsdk:"secret_suffix"` } + diags = plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) + + metadata := infisical.IntegrationMetadata{} + metadata.SecretPrefix = options.SecretPrefix.ValueString() + metadata.SecretSuffix = options.SecretSuffix.ValueString() updatedIntegration, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ IsActive: true, From 0c10ed77376e0e59a1d756cb04687e52f18fb25a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 3 Oct 2024 21:00:44 +0400 Subject: [PATCH 7/7] Update integration_gcp_secret_manager.go --- internal/provider/resource/integration_gcp_secret_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go index d2fceca..c2f5465 100644 --- a/internal/provider/resource/integration_gcp_secret_manager.go +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -356,7 +356,7 @@ func (r *IntegrationGCPSecretManagerResource) Update(ctx context.Context, req re SecretPrefix types.String `tfsdk:"secret_prefix"` SecretSuffix types.String `tfsdk:"secret_suffix"` } - diags = plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) + plan.Options.As(ctx, &options, basetypes.ObjectAsOptions{}) metadata := infisical.IntegrationMetadata{} metadata.SecretPrefix = options.SecretPrefix.ValueString()