Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: base app connection & secret sync setup + GCP #97

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/resources/app_connection_gcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "infisical_app_connection_gcp Resource - terraform-provider-infisical"
subcategory: ""
description: |-
Create and manage GCP App Connection
---

# infisical_app_connection_gcp (Resource)

Create and manage GCP App Connection

## Example Usage

```terraform
terraform {
required_providers {
infisical = {
# version = <latest 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
auth = {
universal = {
client_id = "<machine-identity-client-id>"
client_secret = "<machine-identity-client-secret>"
}
}
}

resource "infisical_app_connection_gcp" "app-connection-gcp" {
name = "gcp-app-connection"
method = "service-account-impersonation"
credentials = {
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
}
description = "I am a test app connection"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `credentials` (Attributes) The credentials for the GCP App Connection (see [below for nested schema](#nestedatt--credentials))
- `method` (String) The method used to authenticate with GCP. Possible values are: service-account-impersonation
- `name` (String) The name of the GCP App Connection to create. Must be slug-friendly

### Optional

- `description` (String) An optional description for the GCP App Connection.

### Read-Only

- `credentials_hash` (String) The hash of the GCP App Connection credentials
- `id` (String) The ID of the app connection

<a id="nestedatt--credentials"></a>
### Nested Schema for `credentials`

Optional:

- `service_account_email` (String, Sensitive) The service account email to connect with GCP. The service account ID (the part of the email before '@') must be suffixed with the first two sections of your organization ID e.g. [email protected]. For more details, refer to the documentation here https://infisical.com/docs/integrations/app-connections/gcp#configure-service-account-for-infisical
100 changes: 100 additions & 0 deletions docs/resources/secret_sync_gcp_secret_manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "infisical_secret_sync_gcp_secret_manager Resource - terraform-provider-infisical"
subcategory: ""
description: |-
Create and manage GCP Secret Manager secret syncs
---

# infisical_secret_sync_gcp_secret_manager (Resource)

Create and manage GCP Secret Manager secret syncs

## Example Usage

```terraform
terraform {
required_providers {
infisical = {
# version = <latest 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
auth = {
universal = {
client_id = "<machine-identity-client-id>"
client_secret = "<machine-identity-client-secret>"
}
}
}

resource "infisical_app_connection_gcp" "app-connection-gcp" {
name = "gcp-app-connect"
method = "service-account-impersonation"
credentials = {
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
}
description = "I am a test app connection"
}

resource "infisical_secret_sync_gcp_secret_manager" "secret_manager_test" {
name = "gcp-sync-tests"
description = "I am a test secret sync"
project_id = "f4517f4c-8b61-4727-8aef-5ae2807126fb"
environment = "prod"
secret_path = "/"
connection_id = infisical_app_connection_gcp.app-connection-gcp.id

sync_options = {
initial_sync_behavior = "import-prioritize-destination"
}
destination_config = {
project_id = "my-duplicate-project"
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `connection_id` (String) The ID of the GCP Connection to use for syncing.
- `destination_config` (Attributes) The destination configuration for the secret sync. (see [below for nested schema](#nestedatt--destination_config))
- `environment` (String) The slug of the project environment to sync secrets from.
- `name` (String) The name of the GCP Secret Manager sync to create. Must be slug-friendly.
- `project_id` (String) The ID of the Infisical project to create the sync in.
- `secret_path` (String) The folder path to sync secrets from.
- `sync_options` (Attributes) Parameters to modify how secrets are synced. (see [below for nested schema](#nestedatt--sync_options))

### Optional

- `auto_sync_enabled` (Boolean) Whether secrets should be automatically synced when changes occur at the source location or not.
- `description` (String) An optional description for the GCP Secret Manager sync.

### Read-Only

- `id` (String) The ID of the GCP Secret Manager secret sync

<a id="nestedatt--destination_config"></a>
### Nested Schema for `destination_config`

Required:

- `project_id` (String) The ID of the GCP project to sync with

Optional:

- `scope` (String) The scope of the sync with GCP Secret Manager. Supported options: global


<a id="nestedatt--sync_options"></a>
### Nested Schema for `sync_options`

Required:

- `initial_sync_behavior` (String) Specify how Infisical should resolve the initial sync to the GCP Secret Manager destination. Supported options: overwrite-destination, import-prioritize-source, import-prioritize-destination
10 changes: 6 additions & 4 deletions examples/resources/infisical_app_connection_gcp/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ provider "infisical" {
}

resource "infisical_app_connection_gcp" "app-connection-gcp" {
name = "gcp-app-connection"
method = "service-account-impersonation"
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
description = "I am a default description"
name = "gcp-app-connection"
method = "service-account-impersonation"
credentials = {
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
}
description = "I am a test app connection"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@ provider "infisical" {
}

resource "infisical_app_connection_gcp" "app-connection-gcp" {
name = "gcp-app-connect"
method = "service-account-impersonation"
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
description = "I am a test app connection"
name = "gcp-app-connect"
method = "service-account-impersonation"
credentials = {
service_account_email = "service-account-df92581a-0fe9@my-duplicate-project.iam.gserviceaccount.com"
}
description = "I am a test app connection"
}

resource "infisical_secret_sync_gcp_secret_manager" "secret_manager" {
name = "gcp-sync"
environment = "dev"
connection_id = infisical_app_connection_gcp.app-connection-gcp.id
secret_path = "/"
gcp_project_id = "my-duplicate-project"
project_id = "f4517f4c-8b61-4727-8aef-5ae2807126fb"
initial_sync_behavior = "overwrite-destination"
description = "I am a test secret sync"
resource "infisical_secret_sync_gcp_secret_manager" "secret_manager_test" {
name = "gcp-sync-tests"
description = "I am a test secret sync"
project_id = "f4517f4c-8b61-4727-8aef-5ae2807126fb"
environment = "prod"
secret_path = "/"
connection_id = infisical_app_connection_gcp.app-connection-gcp.id

sync_options = {
initial_sync_behavior = "import-prioritize-destination"
}
destination_config = {
project_id = "my-duplicate-project"
}
}
4 changes: 4 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
infisical "terraform-provider-infisical/internal/client"
infisicalDatasource "terraform-provider-infisical/internal/provider/datasource"
infisicalResource "terraform-provider-infisical/internal/provider/resource"
appConnectionResource "terraform-provider-infisical/internal/provider/resource/app_connection"
secretSyncResource "terraform-provider-infisical/internal/provider/resource/secret_sync"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
Expand Down Expand Up @@ -260,6 +262,8 @@ func (p *infisicalProvider) Resources(_ context.Context) []func() resource.Resou
infisicalResource.NewSecretApprovalPolicyResource,
infisicalResource.NewAccessApprovalPolicyResource,
infisicalResource.NewProjectSecretImportResource,
appConnectionResource.NewAppConnectionGcpResource,
secretSyncResource.NewSecretSyncGcpSecretManagerResource,
}
}

Expand Down
96 changes: 96 additions & 0 deletions internal/provider/resource/app_connection/app_connection_gcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package resource

import (
"context"
infisical "terraform-provider-infisical/internal/client"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

// AppConnectionGcpCredentialsModel describes the data source data model.
type AppConnectionGcpCredentialsModel struct {
ServiceAccountEmail types.String `tfsdk:"service_account_email"`
}

func NewAppConnectionGcpResource() resource.Resource {
return &AppConnectionBaseResource{
App: infisical.AppConnectionAppGCP,
AppConnectionName: "GCP",
ResourceTypeName: "_app_connection_gcp",
AllowedMethods: []string{"service-account-impersonation"},
CredentialsAttributes: map[string]schema.Attribute{
"service_account_email": schema.StringAttribute{
Optional: true,
Description: "The service account email to connect with GCP. The service account ID (the part of the email before '@') must be suffixed with the first two sections of your organization ID e.g. [email protected]. For more details, refer to the documentation here https://infisical.com/docs/integrations/app-connections/gcp#configure-service-account-for-infisical",
Sensitive: true,
},
},
ReadCredentialsForCreateFromPlan: func(ctx context.Context, plan AppConnectionBaseResourceModel) (map[string]interface{}, diag.Diagnostics) {
credentialsConfig := make(map[string]interface{})

var credentials AppConnectionGcpCredentialsModel
diags := plan.Credentials.As(ctx, &credentials, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, diags
}

if credentials.ServiceAccountEmail.IsNull() || credentials.ServiceAccountEmail.ValueString() == "" {
diags.AddError(
"Unable to create GCP app connection",
"Service account email field must be defined",
)
return nil, diags
}

credentialsConfig["serviceAccountEmail"] = credentials.ServiceAccountEmail.ValueString()

return credentialsConfig, diags
},
ReadCredentialsForUpdateFromPlan: func(ctx context.Context, plan AppConnectionBaseResourceModel, state AppConnectionBaseResourceModel) (map[string]interface{}, diag.Diagnostics) {
credentialsConfig := make(map[string]interface{})

var credentialsFromPlan AppConnectionGcpCredentialsModel
diags := plan.Credentials.As(ctx, &credentialsFromPlan, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, diags
}

var credentialsFromState AppConnectionGcpCredentialsModel
diags = state.Credentials.As(ctx, &credentialsFromState, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, diags
}

if credentialsFromPlan.ServiceAccountEmail.IsNull() || credentialsFromPlan.ServiceAccountEmail.ValueString() == "" {
diags.AddError(
"Unable to update GCP app connection",
"Service account email field must be defined",
)
return nil, diags
}

if credentialsFromState.ServiceAccountEmail.ValueString() != credentialsFromPlan.ServiceAccountEmail.ValueString() {
credentialsConfig["serviceAccountEmail"] = credentialsFromPlan.ServiceAccountEmail.ValueString()
}

return credentialsConfig, diags
},
OverwriteCredentialsFields: func(state *AppConnectionBaseResourceModel) diag.Diagnostics {
credentialsConfig := map[string]attr.Value{
"service_account_email": types.StringNull(),
}

var diags diag.Diagnostics
state.Credentials, diags = types.ObjectValue(map[string]attr.Type{
"service_account_email": types.StringType,
}, credentialsConfig)

return diags
},
}
}
Loading
Loading