generated from hashicorp/terraform-provider-scaffolding
-
Notifications
You must be signed in to change notification settings - Fork 50
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
Implemented HVS Twilio Integration resource #1081
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2530494
* implemented HVS AWS Integration resource
maxcoulombe e0d0d19
Merge remote-tracking branch 'origin/main' into vault-29949/HVS-AWS-I…
maxcoulombe 304bbf0
+ implemented HVS Twilio Integration resource
maxcoulombe ad31060
+ changelog
maxcoulombe f4a2e28
Merge branch 'main' into vault-29949/Twilio-Integration-Resource
maxcoulombe 984ca54
* fix merge
maxcoulombe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
add vault_secrets_integration_twilio resource | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "hcp_vault_secrets_integration_twilio Resource - terraform-provider-hcp" | ||
subcategory: "" | ||
description: |- | ||
The Vault Secrets Twilio integration resource manages a Twilio integration. | ||
--- | ||
|
||
# hcp_vault_secrets_integration_twilio (Resource) | ||
|
||
The Vault Secrets Twilio integration resource manages a Twilio integration. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "hcp_vault_secrets_integration_twilio" "example" { | ||
name = "my-twilio-1" | ||
capabilities = ["ROTATION"] | ||
static_credential_details = { | ||
account_sid = "AC7..." | ||
api_key_sid = "TKa..." | ||
api_key_secret = "6aG..." | ||
} | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `capabilities` (Set of String) Capabilities enabled for the integration. See the Vault Secrets documentation for the list of supported capabilities per provider. | ||
- `name` (String) The Vault Secrets integration name. | ||
|
||
### Optional | ||
|
||
- `project_id` (String) HCP project ID that owns the HCP Vault Secrets integration. Inferred from the provider configuration if omitted. | ||
- `static_credential_details` (Attributes) Twilio API key parts used to authenticate against the target Twilio account. (see [below for nested schema](#nestedatt--static_credential_details)) | ||
|
||
### Read-Only | ||
|
||
- `organization_id` (String) HCP organization ID that owns the HCP Vault Secrets integration. | ||
- `resource_id` (String) Resource ID used to uniquely identify the integration instance on the HCP platform. | ||
- `resource_name` (String) Resource name used to uniquely identify the integration instance on the HCP platform. | ||
|
||
<a id="nestedatt--static_credential_details"></a> | ||
### Nested Schema for `static_credential_details` | ||
|
||
Required: | ||
|
||
- `account_sid` (String) Account SID for the target Twilio account. | ||
- `api_key_secret` (String, Sensitive) Api key secret used with the api key SID to authenticate against the target Twilio account. | ||
- `api_key_sid` (String) Api key SID to authenticate against the target Twilio account. | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
# Vault Secrets Twilio Integration can be imported by specifying the name of the integration | ||
# Note that since the Api Key secret is never returned on the Vault Secrets API, | ||
# the next plan or apply will show a diff for that field. | ||
terraform import hcp_vault_secrets_integration_twilio.example my-twilio-1 | ||
``` |
4 changes: 4 additions & 0 deletions
4
examples/resources/hcp_vault_secrets_integration_twilio/import.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Vault Secrets Twilio Integration can be imported by specifying the name of the integration | ||
# Note that since the Api Key secret is never returned on the Vault Secrets API, | ||
# the next plan or apply will show a diff for that field. | ||
terraform import hcp_vault_secrets_integration_twilio.example my-twilio-1 |
9 changes: 9 additions & 0 deletions
9
examples/resources/hcp_vault_secrets_integration_twilio/resource.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
resource "hcp_vault_secrets_integration_twilio" "example" { | ||
name = "my-twilio-1" | ||
capabilities = ["ROTATION"] | ||
static_credential_details = { | ||
account_sid = "AC7..." | ||
api_key_sid = "TKa..." | ||
api_key_secret = "6aG..." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
303 changes: 303 additions & 0 deletions
303
internal/provider/vaultsecrets/resource_vault_secrets_integration_twilio.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vaultsecrets | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/client/secret_service" | ||
secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/models" | ||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"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/types" | ||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" | ||
"github.com/hashicorp/terraform-provider-hcp/internal/clients" | ||
"github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers" | ||
"golang.org/x/exp/maps" | ||
) | ||
|
||
type IntegrationTwilio struct { | ||
// Input fields | ||
ProjectID types.String `tfsdk:"project_id"` | ||
Name types.String `tfsdk:"name"` | ||
Capabilities types.Set `tfsdk:"capabilities"` | ||
StaticCredentialDetails types.Object `tfsdk:"static_credential_details"` | ||
|
||
// Computed fields | ||
OrganizationID types.String `tfsdk:"organization_id"` | ||
ResourceID types.String `tfsdk:"resource_id"` | ||
ResourceName types.String `tfsdk:"resource_name"` | ||
|
||
// Inner API-compatible models derived from the Terraform fields | ||
capabilities []*secretmodels.Secrets20231128Capability `tfsdk:"-"` | ||
staticCredentialDetails *secretmodels.Secrets20231128TwilioStaticCredentialsRequest `tfsdk:"-"` | ||
} | ||
|
||
// Helper structs to help populate concrete targets from types.Object fields | ||
type staticCredentialDetails struct { | ||
AccountSID types.String `tfsdk:"account_sid"` | ||
APIKeySID types.String `tfsdk:"api_key_sid"` | ||
APIKeySecret types.String `tfsdk:"api_key_secret"` | ||
} | ||
|
||
var _ resource.Resource = &resourceVaultSecretsIntegrationTwilio{} | ||
var _ resource.ResourceWithConfigure = &resourceVaultSecretsIntegrationTwilio{} | ||
var _ resource.ResourceWithModifyPlan = &resourceVaultSecretsIntegrationTwilio{} | ||
var _ resource.ResourceWithImportState = &resourceVaultSecretsIntegrationTwilio{} | ||
|
||
func NewVaultSecretsIntegrationTwilioResource() resource.Resource { | ||
return &resourceVaultSecretsIntegrationTwilio{} | ||
} | ||
|
||
type resourceVaultSecretsIntegrationTwilio struct { | ||
client *clients.Client | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_vault_secrets_integration_twilio" | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
attributes := map[string]schema.Attribute{ | ||
"static_credential_details": schema.SingleNestedAttribute{ | ||
Description: "Twilio API key parts used to authenticate against the target Twilio account.", | ||
Optional: true, | ||
Attributes: map[string]schema.Attribute{ | ||
"account_sid": schema.StringAttribute{ | ||
Description: "Account SID for the target Twilio account.", | ||
Required: true, | ||
}, | ||
"api_key_sid": schema.StringAttribute{ | ||
Description: "Api key SID to authenticate against the target Twilio account.", | ||
Required: true, | ||
}, | ||
"api_key_secret": schema.StringAttribute{ | ||
Description: "Api key secret used with the api key SID to authenticate against the target Twilio account.", | ||
Required: true, | ||
Sensitive: true, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
maps.Copy(attributes, sharedIntegrationAttributes) | ||
|
||
resp.Schema = schema.Schema{ | ||
MarkdownDescription: "The Vault Secrets Twilio integration resource manages a Twilio integration.", | ||
Attributes: attributes, | ||
} | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
client, ok := req.ProviderData.(*clients.Client) | ||
if !ok { | ||
resp.Diagnostics.AddError( | ||
"Unexpected Data Source Configure Type", | ||
fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), | ||
) | ||
return | ||
} | ||
r.client = client | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { | ||
modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp) | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { | ||
resp.Diagnostics.Append(decorateOperation[*IntegrationTwilio](ctx, r.client, &resp.State, req.State.Get, "reading", func(i integration) (any, error) { | ||
integration, ok := i.(*IntegrationTwilio) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid integration type, expected *IntegrationTwilio, got: %T, this is a bug on the provider", i) | ||
} | ||
|
||
response, err := r.client.VaultSecretsPreview.GetTwilioIntegration( | ||
secret_service.NewGetTwilioIntegrationParamsWithContext(ctx). | ||
WithOrganizationID(integration.OrganizationID.ValueString()). | ||
WithProjectID(integration.ProjectID.ValueString()). | ||
WithName(integration.Name.ValueString()), nil) | ||
if err != nil && !clients.IsResponseCodeNotFound(err) { | ||
return nil, err | ||
} | ||
if response == nil || response.Payload == nil { | ||
return nil, nil | ||
} | ||
return response.Payload.Integration, nil | ||
})...) | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { | ||
resp.Diagnostics.Append(decorateOperation[*IntegrationTwilio](ctx, r.client, &resp.State, req.Plan.Get, "creating", func(i integration) (any, error) { | ||
integration, ok := i.(*IntegrationTwilio) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid integration type, expected *IntegrationTwilio, got: %T, this is a bug on the provider", i) | ||
} | ||
|
||
response, err := r.client.VaultSecretsPreview.CreateTwilioIntegration(&secret_service.CreateTwilioIntegrationParams{ | ||
Body: &secretmodels.SecretServiceCreateTwilioIntegrationBody{ | ||
Capabilities: integration.capabilities, | ||
StaticCredentialDetails: integration.staticCredentialDetails, | ||
Name: integration.Name.ValueString(), | ||
}, | ||
OrganizationID: integration.OrganizationID.ValueString(), | ||
ProjectID: integration.ProjectID.ValueString(), | ||
}, nil) | ||
if err != nil && !clients.IsResponseCodeNotFound(err) { | ||
return nil, err | ||
} | ||
if response == nil || response.Payload == nil { | ||
return nil, nil | ||
} | ||
return response.Payload.Integration, nil | ||
})...) | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { | ||
resp.Diagnostics.Append(decorateOperation[*IntegrationTwilio](ctx, r.client, &resp.State, req.Plan.Get, "updating", func(i integration) (any, error) { | ||
integration, ok := i.(*IntegrationTwilio) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid integration type, expected *IntegrationTwilio, got: %T, this is a bug on the provider", i) | ||
} | ||
|
||
response, err := r.client.VaultSecretsPreview.UpdateTwilioIntegration(&secret_service.UpdateTwilioIntegrationParams{ | ||
Body: &secretmodels.SecretServiceUpdateTwilioIntegrationBody{ | ||
Capabilities: integration.capabilities, | ||
StaticCredentialDetails: integration.staticCredentialDetails, | ||
}, | ||
Name: integration.Name.ValueString(), | ||
OrganizationID: integration.OrganizationID.ValueString(), | ||
ProjectID: integration.ProjectID.ValueString(), | ||
}, nil) | ||
if err != nil && !clients.IsResponseCodeNotFound(err) { | ||
return nil, err | ||
} | ||
if response == nil || response.Payload == nil { | ||
return nil, nil | ||
} | ||
return response.Payload.Integration, nil | ||
})...) | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { | ||
resp.Diagnostics.Append(decorateOperation[*IntegrationTwilio](ctx, r.client, &resp.State, req.State.Get, "deleting", func(i integration) (any, error) { | ||
integration, ok := i.(*IntegrationTwilio) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid integration type, expected *IntegrationTwilio, got: %T, this is a bug on the provider", i) | ||
} | ||
|
||
_, err := r.client.VaultSecretsPreview.DeleteTwilioIntegration( | ||
secret_service.NewDeleteTwilioIntegrationParamsWithContext(ctx). | ||
WithOrganizationID(integration.OrganizationID.ValueString()). | ||
WithProjectID(integration.ProjectID.ValueString()). | ||
WithName(integration.Name.ValueString()), nil) | ||
if err != nil && !clients.IsResponseCodeNotFound(err) { | ||
return nil, err | ||
} | ||
return nil, nil | ||
})...) | ||
} | ||
|
||
func (r *resourceVaultSecretsIntegrationTwilio) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { | ||
// The Vault Secrets API does not return sensitive values like the secret access key, so they will be initialized to an empty value | ||
// It means the first plan/apply after a successful import will always show a diff for the secret access key | ||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), r.client.Config.OrganizationID)...) | ||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), r.client.Config.ProjectID)...) | ||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), req.ID)...) | ||
} | ||
|
||
var _ integration = &IntegrationTwilio{} | ||
|
||
func (i *IntegrationTwilio) projectID() types.String { | ||
return i.ProjectID | ||
|
||
} | ||
func (i *IntegrationTwilio) initModel(ctx context.Context, orgID, projID string) diag.Diagnostics { | ||
// Init fields that depend on the Terraform provider configuration | ||
i.OrganizationID = types.StringValue(orgID) | ||
i.ProjectID = types.StringValue(projID) | ||
|
||
// Init the HVS domain models from the Terraform domain models | ||
var capabilities []types.String | ||
diags := i.Capabilities.ElementsAs(ctx, &capabilities, false) | ||
if diags.HasError() { | ||
return diags | ||
} | ||
for _, c := range capabilities { | ||
i.capabilities = append(i.capabilities, secretmodels.Secrets20231128Capability(c.ValueString()).Pointer()) | ||
} | ||
|
||
if !i.StaticCredentialDetails.IsNull() { | ||
scd := staticCredentialDetails{} | ||
diags = i.StaticCredentialDetails.As(ctx, &scd, basetypes.ObjectAsOptions{}) | ||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
i.staticCredentialDetails = &secretmodels.Secrets20231128TwilioStaticCredentialsRequest{ | ||
AccountSid: scd.AccountSID.ValueString(), | ||
APIKeySecret: scd.APIKeySecret.ValueString(), | ||
APIKeySid: scd.APIKeySID.ValueString(), | ||
} | ||
} | ||
|
||
return diag.Diagnostics{} | ||
} | ||
|
||
func (i *IntegrationTwilio) fromModel(ctx context.Context, orgID, projID string, model any) diag.Diagnostics { | ||
diags := diag.Diagnostics{} | ||
|
||
integrationModel, ok := model.(*secretmodels.Secrets20231128TwilioIntegration) | ||
if !ok { | ||
diags.AddError("Invalid model type, this is a bug on the provider.", fmt.Sprintf("Expected *secretmodels.Secrets20231128TwilioIntegration, got: %T", model)) | ||
return diags | ||
} | ||
|
||
i.OrganizationID = types.StringValue(orgID) | ||
i.ProjectID = types.StringValue(projID) | ||
// TODO These fields are not returned by the API on updates, so we use the state value if they are blank on the model as a stopgap until it gets fixed on HVS | ||
if integrationModel.ResourceID != "" { | ||
i.ResourceID = types.StringValue(integrationModel.ResourceID) | ||
} | ||
if integrationModel.ResourceName != "" { | ||
i.ResourceName = types.StringValue(integrationModel.ResourceName) | ||
} | ||
if integrationModel.Name != "" { | ||
i.Name = types.StringValue(integrationModel.Name) | ||
} | ||
|
||
var values []attr.Value | ||
for _, c := range integrationModel.Capabilities { | ||
values = append(values, types.StringValue(string(*c))) | ||
} | ||
i.Capabilities, diags = types.SetValue(types.StringType, values) | ||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
if integrationModel.StaticCredentialDetails != nil { | ||
// The secret key is not returned by the API, so we use an empty value (e.g. for imports) or the state value (e.g. for updates) | ||
apiKeySecret := "" | ||
if i.staticCredentialDetails != nil { | ||
apiKeySecret = i.staticCredentialDetails.APIKeySecret | ||
} | ||
|
||
i.StaticCredentialDetails, diags = types.ObjectValue(i.StaticCredentialDetails.AttributeTypes(ctx), map[string]attr.Value{ | ||
"account_sid": types.StringValue(integrationModel.StaticCredentialDetails.AccountSid), | ||
"api_key_sid": types.StringValue(integrationModel.StaticCredentialDetails.APIKeySid), | ||
"api_key_secret": types.StringValue(apiKeySecret), | ||
}) | ||
if diags.HasError() { | ||
return diags | ||
} | ||
} | ||
|
||
return diags | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: is the error expected to be formatted in Title Case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, this config block is used by ~30 different resources this is just some shared boilerplate so I'm not sure if this was intended but I'll keep it for consistency with the others.