diff --git a/docs/resources/secretstore.md b/docs/resources/secretstore.md new file mode 100644 index 0000000..f86b98f --- /dev/null +++ b/docs/resources/secretstore.md @@ -0,0 +1,133 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "humanitec_secretstore Resource - terraform-provider-humanitec" +subcategory: "" +description: |- + An external secret management system used by an organization to store secrets referenced in Humanitec. +--- + +# humanitec_secretstore (Resource) + +An external secret management system used by an organization to store secrets referenced in Humanitec. + +## Example Usage + +```terraform +resource "humanitec_secretstore" "secret_store_gcpsm" { + id = "secretstore_id" + gcpsm = { + project_id = "example-project" + auth = { + secret_access_key = "secret-access-key" + } + } +} +``` + + +## Schema + +### Required + +- `id` (String) The ID of the Secret Store. + +### Optional + +- `awssm` (Attributes) AWS Secret Manager specification. (see [below for nested schema](#nestedatt--awssm)) +- `azurekv` (Attributes) Azure KV Secret Manager specification. (see [below for nested schema](#nestedatt--azurekv)) +- `gcpsm` (Attributes) GCP Secret Manager specification. (see [below for nested schema](#nestedatt--gcpsm)) +- `primary` (Boolean) Whether the Secret Store is the Primary one for the organization. +- `vault` (Attributes) Vault specification. (see [below for nested schema](#nestedatt--vault)) + + +### Nested Schema for `awssm` + +Required: + +- `region` (String) The region of AWS Secret Manager. + +Optional: + +- `auth` (Attributes, Sensitive) Credentials to authenticate to AWS Secret Manager. (see [below for nested schema](#nestedatt--awssm--auth)) + + +### Nested Schema for `awssm.auth` + +Required: + +- `access_key_id` (String) The Access Key ID. +- `secret_access_key` (String) The Secret Access Key. + + + + +### Nested Schema for `azurekv` + +Required: + +- `tenant_id` (String) The AzureKV Tenant ID. +- `url` (String) The AzureKV URL. + +Optional: + +- `auth` (Attributes, Sensitive) Credentials to authenticate to Azure Key Vault. (see [below for nested schema](#nestedatt--azurekv--auth)) + + +### Nested Schema for `azurekv.auth` + +Required: + +- `client_id` (String) The AzureKV Client ID. +- `client_secret` (String) The AzureKV Client Secret. + + + + +### Nested Schema for `gcpsm` + +Required: + +- `project_id` (String) The project ID of the GCPSM. + +Optional: + +- `auth` (Attributes, Sensitive) Credentials to authenticate the GCPSM. (see [below for nested schema](#nestedatt--gcpsm--auth)) + + +### Nested Schema for `gcpsm.auth` + +Required: + +- `secret_access_key` (String) The Secret Access Key. + + + + +### Nested Schema for `vault` + +Required: + +- `url` (String) The Vault URL. + +Optional: + +- `agent_id` (String) Reference to the agent to use to hit Vault. +- `auth` (Attributes, Sensitive) Credentials to authenticate the Vault. (see [below for nested schema](#nestedatt--vault--auth)) +- `path` (String) The path used to read / write secrets. + + +### Nested Schema for `vault.auth` + +Optional: + +- `role` (String) Role to assume to access Vault. +- `token` (String) Token to access Vault. + +## Import + +Import is supported using the following syntax: + +```shell +# import an existing secret store +terraform import humanitec_secretstore.secret_store_gcpsm secretstore_id +``` diff --git a/examples/resources/humanitec_secretstore/import.sh b/examples/resources/humanitec_secretstore/import.sh new file mode 100644 index 0000000..e89ce00 --- /dev/null +++ b/examples/resources/humanitec_secretstore/import.sh @@ -0,0 +1,3 @@ +# import an existing secret store +terraform import humanitec_secretstore.secret_store_gcpsm secretstore_id + diff --git a/examples/resources/humanitec_secretstore/resource.tf b/examples/resources/humanitec_secretstore/resource.tf new file mode 100644 index 0000000..1694317 --- /dev/null +++ b/examples/resources/humanitec_secretstore/resource.tf @@ -0,0 +1,9 @@ +resource "humanitec_secretstore" "secret_store_gcpsm" { + id = "secretstore_id" + gcpsm = { + project_id = "example-project" + auth = { + secret_access_key = "secret-access-key" + } + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 524ac47..166deb2 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -163,6 +163,7 @@ func (p *HumanitecProvider) Resources(ctx context.Context) []func() resource.Res NewResourcePipeline, NewResourceResourceDriver, NewResourceRule, + NewResourceSecretStore, NewResourceValue, NewResourceWebhook, } diff --git a/internal/provider/resource_secret_store.go b/internal/provider/resource_secret_store.go new file mode 100644 index 0000000..0ea86d3 --- /dev/null +++ b/internal/provider/resource_secret_store.go @@ -0,0 +1,469 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/booldefault" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/humanitec/humanitec-go-autogen" + "github.com/humanitec/humanitec-go-autogen/client" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &SecretStore{} +var _ resource.ResourceWithImportState = &SecretStore{} + +func NewResourceSecretStore() resource.Resource { + return &SecretStore{} +} + +// SecretStore defines the resource implementation. +type SecretStore struct { + client *humanitec.Client + orgId string +} + +// SecretStoreModel describes the app data model. +type SecretStoreModel struct { + ID types.String `tfsdk:"id"` + Primary types.Bool `tfsdk:"primary"` + AwsSM *AwsSMModel `tfsdk:"awssm"` + AzureKV *AzureKVModel `tfsdk:"azurekv"` + GcpSM *GcpSMModel `tfsdk:"gcpsm"` + Vault *VaultModel `tfsdk:"vault"` +} + +type AwsSMModel struct { + Auth *AwsAuthModel `tfsdk:"auth"` + Region types.String `tfsdk:"region"` +} + +type AwsAuthModel struct { + AccessKeyID types.String `tfsdk:"access_key_id"` + SecretAccessKey types.String `tfsdk:"secret_access_key"` +} + +type AzureKVModel struct { + Auth *AzureKVAuthModel `tfsdk:"auth"` + TenantID types.String `tfsdk:"tenant_id"` + Url types.String `tfsdk:"url"` +} + +type AzureKVAuthModel struct { + ClientID types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` +} + +type GcpSMModel struct { + Auth *GcpAuthModel `tfsdk:"auth"` + ProjectID types.String `tfsdk:"project_id"` +} + +type GcpAuthModel struct { + SecretAccessKey types.String `tfsdk:"secret_access_key"` +} + +type VaultModel struct { + Auth *VaultAuthModel `tfsdk:"auth"` + AgentID types.String `tfsdk:"agent_id"` + Path types.String `tfsdk:"path"` + Url types.String `tfsdk:"url"` +} + +type VaultAuthModel struct { + Role types.String `tfsdk:"role"` + Token types.String `tfsdk:"token"` +} + +func (*SecretStore) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_secretstore" +} + +func (*SecretStore) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "An external secret management system used by an organization to store secrets referenced in Humanitec.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The ID of the Secret Store.", + Required: true, + }, + "primary": schema.BoolAttribute{ + MarkdownDescription: "Whether the Secret Store is the Primary one for the organization.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "awssm": schema.SingleNestedAttribute{ + MarkdownDescription: "AWS Secret Manager specification.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "region": schema.StringAttribute{ + MarkdownDescription: "The region of AWS Secret Manager.", + Required: true, + }, + "auth": schema.SingleNestedAttribute{ + MarkdownDescription: "Credentials to authenticate to AWS Secret Manager.", + Sensitive: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "access_key_id": schema.StringAttribute{ + MarkdownDescription: "The Access Key ID.", + Required: true, + }, + "secret_access_key": schema.StringAttribute{ + MarkdownDescription: "The Secret Access Key.", + Required: true, + }, + }, + }, + }, + }, + "azurekv": schema.SingleNestedAttribute{ + MarkdownDescription: "Azure KV Secret Manager specification.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "tenant_id": schema.StringAttribute{ + MarkdownDescription: "The AzureKV Tenant ID.", + Required: true, + }, + "url": schema.StringAttribute{ + MarkdownDescription: "The AzureKV URL.", + Required: true, + }, + "auth": schema.SingleNestedAttribute{ + MarkdownDescription: "Credentials to authenticate to Azure Key Vault.", + Sensitive: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "client_id": schema.StringAttribute{ + MarkdownDescription: "The AzureKV Client ID.", + Required: true, + }, + "client_secret": schema.StringAttribute{ + MarkdownDescription: "The AzureKV Client Secret.", + Required: true, + }, + }, + }, + }, + }, + "gcpsm": schema.SingleNestedAttribute{ + MarkdownDescription: "GCP Secret Manager specification.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + MarkdownDescription: "The project ID of the GCPSM.", + Required: true, + }, + "auth": schema.SingleNestedAttribute{ + MarkdownDescription: "Credentials to authenticate the GCPSM.", + Sensitive: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "secret_access_key": schema.StringAttribute{ + MarkdownDescription: "The Secret Access Key.", + Required: true, + }, + }, + }, + }, + }, + "vault": schema.SingleNestedAttribute{ + MarkdownDescription: "Vault specification.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The Vault URL.", + }, + "path": schema.StringAttribute{ + MarkdownDescription: "The path used to read / write secrets.", + Optional: true, + }, + "agent_id": schema.StringAttribute{ + MarkdownDescription: "Reference to the agent to use to hit Vault.", + Optional: true, + }, + "auth": schema.SingleNestedAttribute{ + MarkdownDescription: "Credentials to authenticate the Vault.", + Sensitive: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "token": schema.StringAttribute{ + MarkdownDescription: "Token to access Vault.", + Optional: true, + }, + "role": schema.StringAttribute{ + MarkdownDescription: "Role to assume to access Vault.", + Optional: true, + }, + }, + }, + }, + }, + }, + } +} + +func (s *SecretStore) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + resdata, ok := req.ProviderData.(*HumanitecData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + s.client = resdata.Client + s.orgId = resdata.OrgID +} + +func parseSecretStoreResponse(res *client.SecretStoreResponse, data *SecretStoreModel) { + data.ID = types.StringValue(res.Id) + data.Primary = types.BoolValue(res.Primary) + if res.Awssm != nil { + if data.AwsSM == nil { + data.AwsSM = &AwsSMModel{} + } + if res.Awssm.Region != nil { + data.AwsSM.Region = types.StringValue(*res.Awssm.Region) + } + } else if res.Azurekv != nil { + if data.AzureKV == nil { + data.AzureKV = &AzureKVModel{} + } + if res.Azurekv.TenantId != nil { + data.AzureKV.TenantID = types.StringValue(*res.Azurekv.TenantId) + } + if res.Azurekv.Url != nil { + data.AzureKV.Url = types.StringValue(*res.Azurekv.Url) + } + } else if res.Gcpsm != nil { + if data.GcpSM == nil { + data.GcpSM = &GcpSMModel{} + } + if res.Gcpsm.ProjectId != nil { + data.GcpSM.ProjectID = types.StringValue(*res.Gcpsm.ProjectId) + } + } else if res.Vault != nil { + if data.Vault == nil { + data.Vault = &VaultModel{} + } + if res.Vault.AgentId != nil { + data.Vault.AgentID = types.StringValue(*res.Vault.AgentId) + } + if res.Vault.Path != nil { + data.Vault.Path = types.StringValue(*res.Vault.Path) + } + if res.Vault.Url != nil { + data.Vault.Url = types.StringValue(*res.Vault.Url) + } + } +} + +func toSecretStoreRequest(data *SecretStoreModel) (*client.CreateSecretStorePayloadRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + secretStorePayload := &client.CreateSecretStorePayloadRequest{ + Id: data.ID.ValueString(), + Primary: data.Primary.ValueBool(), + } + if data.AwsSM != nil { + secretStorePayload.Awssm = &client.AWSSMRequest{ + Region: data.AwsSM.Region.ValueStringPointer(), + } + if data.AwsSM.Auth != nil { + secretStorePayload.Awssm.Auth = &client.AWSAuthRequest{ + AccessKeyId: data.AwsSM.Auth.AccessKeyID.ValueStringPointer(), + SecretAccessKey: data.AwsSM.Auth.SecretAccessKey.ValueStringPointer(), + } + } + } else if data.AzureKV != nil { + secretStorePayload.Azurekv = &client.AzureKVRequest{ + TenantId: data.AzureKV.TenantID.ValueStringPointer(), + Url: data.AzureKV.Url.ValueStringPointer(), + } + if data.AzureKV.Auth != nil { + secretStorePayload.Azurekv.Auth = &client.AzureAuthRequest{ + ClientId: data.AzureKV.Auth.ClientID.ValueStringPointer(), + ClientSecret: data.AzureKV.Auth.ClientSecret.ValueStringPointer(), + } + } + } else if data.GcpSM != nil { + secretStorePayload.Gcpsm = &client.GCPSMRequest{ + ProjectId: data.GcpSM.ProjectID.ValueStringPointer(), + } + if data.GcpSM.Auth != nil { + secretStorePayload.Gcpsm.Auth = &client.GCPAuthRequest{ + SecretAccessKey: data.GcpSM.Auth.SecretAccessKey.ValueStringPointer(), + } + } + } else if data.Vault != nil { + secretStorePayload.Vault = &client.VaultRequest{ + AgentId: data.Vault.AgentID.ValueStringPointer(), + Url: data.Vault.Url.ValueStringPointer(), + Path: data.Vault.Path.ValueStringPointer(), + } + if data.Vault.Auth != nil { + secretStorePayload.Vault.Auth = &client.VaultAuthRequest{ + Token: data.Vault.Auth.Token.ValueStringPointer(), + Role: data.Vault.Auth.Role.ValueStringPointer(), + } + } + } + + return secretStorePayload, diags +} + +func (s *SecretStore) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *SecretStoreModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + httpBody, diags := toSecretStoreRequest(data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + httpResp, err := s.client.PostOrgsOrgIdSecretstoresWithResponse(ctx, s.orgId, *httpBody) + if err != nil { + resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to create secret role, got error: %s", err)) + return + } + + if httpResp.StatusCode() != 201 { + resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to create secret store, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Read implements resource.Resource. +func (s *SecretStore) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *SecretStoreModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + id := data.ID.ValueString() + + httpResp, err := s.client.GetOrgsOrgIdSecretstoresStoreIdWithResponse(ctx, s.orgId, id) + if err != nil { + resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to read secret store, got error: %s", err)) + return + } + if httpResp.StatusCode() != 200 { + resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to read secret store, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) + return + } + + if httpResp.JSON200 == nil { + resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to read secret store, missing body, body: %s", httpResp.Body)) + return + } + + parseSecretStoreResponse(httpResp.JSON200, data) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + +} + +func (s *SecretStore) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data, state *SecretStoreModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id := state.ID.ValueString() + + createBody, diags := toSecretStoreRequest(data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var updateBody client.UpdateSecretStorePayloadRequest + updateBody.Primary = &createBody.Primary + if createBody.Awssm != nil { + updateBody.Awssm = createBody.Awssm + } else if createBody.Azurekv != nil { + updateBody.Azurekv = createBody.Azurekv + } else if createBody.Gcpsm != nil { + updateBody.Gcpsm = createBody.Gcpsm + } else if createBody.Vault != nil { + updateBody.Vault = createBody.Vault + } + + httpResp, err := s.client.PatchOrgsOrgIdSecretstoresStoreIdWithResponse(ctx, s.orgId, id, updateBody) + if err != nil { + resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to update secret store, got error: %s", err)) + return + } + + if httpResp.StatusCode() != 200 { + resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to update secret store, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) + return + } + + parseSecretStoreResponse(httpResp.JSON200, data) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (s *SecretStore) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *SecretStoreModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + id := data.ID.ValueString() + + httpResp, err := s.client.DeleteOrgsOrgIdSecretstoresStoreIdWithResponse(ctx, s.orgId, id) + if err != nil { + resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to delete secret store, got error: %s", err)) + return + } + + if httpResp.StatusCode() != 204 { + resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to delete secret store, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) + return + } +} + +func (s *SecretStore) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/resource_secret_store_test.go b/internal/provider/resource_secret_store_test.go new file mode 100644 index 0000000..e1c2627 --- /dev/null +++ b/internal/provider/resource_secret_store_test.go @@ -0,0 +1,283 @@ +package provider + +import ( + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceSecretStore_AzureKV(t *testing.T) { + id := fmt.Sprintf("azurekv-test-%d", time.Now().UnixNano()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccSecretStoreAzureKV(id, "tenant-id", "azurekv-url", "client-id", "client-secret"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "primary", "false"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.tenant_id", "tenant-id"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.url", "azurekv-url"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.auth.client_id", "client-id"), + ), + }, + // ImportState testing + { + ResourceName: "humanitec_secretstore.secret_store_azurekv_test", + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return id, nil + }, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"azurekv.auth"}, + }, + // Update and Read testing + { + Config: testAccSecretStoreAzureKV(id, "tenant-id", "azurekv-url-changed", "client-id-changed", "client-secret"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "primary", "false"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.tenant_id", "tenant-id"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.url", "azurekv-url-changed"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_azurekv_test", "azurekv.auth.client_id", "client-id-changed"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccResourceSecretStore_Aws(t *testing.T) { + id := fmt.Sprintf("awssm-test-%d", time.Now().UnixNano()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccSecretStoreAwsSM(id, "access-key-id", "secret-access-key"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "primary", "true"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "awssm.region", "eu-central-1"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "awssm.auth.access_key_id", "access-key-id"), + ), + }, + // ImportState testing + { + ResourceName: "humanitec_secretstore.secret_store_awssm_test", + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return id, nil + }, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"awssm.auth"}, + }, + // Update and Read testing + { + Config: testAccSecretStoreAwsSM(id, "access-key-id-changed", "secret-access-key"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "primary", "true"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "awssm.region", "eu-central-1"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_awssm_test", "awssm.auth.access_key_id", "access-key-id-changed"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccResourceSecretStore_GcpSM(t *testing.T) { + id := fmt.Sprintf("gcpsm-test-%d", time.Now().UnixNano()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccSecretStoreGcpSM(id, "gcp-project", "secret-access-key"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_gcpsm_test", "gcpsm.project_id", "gcp-project"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_gcpsm_test", "gcpsm.auth.secret_access_key", "secret-access-key"), + ), + }, + // ImportState testing + { + ResourceName: "humanitec_secretstore.secret_store_gcpsm_test", + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return id, nil + }, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"gcpsm.auth"}, + }, + // Update and Read testing + { + Config: testAccSecretStoreGcpSM(id, "gcp-project-changed", "secret-access-key-changed"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_gcpsm_test", "gcpsm.project_id", "gcp-project-changed"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_gcpsm_test", "gcpsm.auth.secret_access_key", "secret-access-key-changed"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccResourceSecretStore_Vault(t *testing.T) { + id := fmt.Sprintf("vault-test-%d", time.Now().UnixNano()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccSecretStoreVault(id, "vault-url", "vault-token", false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "primary", "false"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.url", "vault-url"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.auth.token", "vault-token"), + ), + }, + // ImportState testing + { + ResourceName: "humanitec_secretstore.secret_store_vault_test", + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return id, nil + }, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vault.auth"}, + }, + // Update and Read testing + { + Config: testAccSecretStoreVault(id, "vault-url-changed", "vault-token-changed", true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "primary", "true"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.url", "vault-url-changed"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.auth.token", "vault-token-changed"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccResourceSecretStore_Vault_RemoveAuth(t *testing.T) { + id := fmt.Sprintf("vault-test-%d", time.Now().UnixNano()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccSecretStoreVault(id, "vault-url", "vault-token", false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "primary", "false"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.url", "vault-url"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.auth.token", "vault-token"), + ), + }, + // ImportState testing + { + ResourceName: "humanitec_secretstore.secret_store_vault_test", + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return id, nil + }, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vault.auth"}, + }, + // Update and Read testing + { + Config: testAccSecretStoreVaultNoAuth(id, "vault-url", false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "primary", "false"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.url", "vault-url"), + resource.TestCheckResourceAttr("humanitec_secretstore.secret_store_vault_test", "vault.auth.%", "0"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccSecretStoreAzureKV(storeID, tenantID, url, clientID, clientSecret string) string { + return fmt.Sprintf(` + resource "humanitec_secretstore" "secret_store_azurekv_test" { + id = "%s" + azurekv = { + tenant_id = "%s" + url = "%s" + auth = { + client_id = "%s" + client_secret = "%s" + } + } + } +`, storeID, tenantID, url, clientID, clientSecret) +} + +func testAccSecretStoreAwsSM(storeID, accessKeyID, secretAccessKey string) string { + return fmt.Sprintf(` + resource "humanitec_secretstore" "secret_store_awssm_test" { + id = "%s" + primary = true + awssm = { + region = "eu-central-1" + auth = { + access_key_id = "%s" + secret_access_key = "%s" + } + } + } +`, storeID, accessKeyID, secretAccessKey) +} + +func testAccSecretStoreGcpSM(storeID, projectID, secretAccessKey string) string { + return fmt.Sprintf(` + resource "humanitec_secretstore" "secret_store_gcpsm_test" { + id = "%s" + gcpsm = { + project_id = "%s" + auth = { + secret_access_key = "%s" + } + } + } +`, storeID, projectID, secretAccessKey) +} + +func testAccSecretStoreVault(storeID, url, token string, primary bool) string { + return fmt.Sprintf(` + resource "humanitec_secretstore" "secret_store_vault_test" { + id = "%s" + primary = %v + vault = { + url = "%s" + auth = { + token = "%s" + } + } + } +`, storeID, primary, url, token) +} + +func testAccSecretStoreVaultNoAuth(storeID, url string, primary bool) string { + return fmt.Sprintf(` + resource "humanitec_secretstore" "secret_store_vault_test" { + id = "%s" + primary = %v + vault = { + url = "%s" + } + } +`, storeID, primary, url) +}