diff --git a/.changelog/810.txt b/.changelog/810.txt new file mode 100644 index 000000000..0820a8c3b --- /dev/null +++ b/.changelog/810.txt @@ -0,0 +1,3 @@ +```release-note:improvement + Add GitHub Action to run identity-specific tests + ``` \ No newline at end of file diff --git a/.changelog/812.txt b/.changelog/812.txt new file mode 100644 index 000000000..a04947336 --- /dev/null +++ b/.changelog/812.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Documentation: Gracefully handle rate limiting error on `hcp_vault_secrets_secret` resource. +``` diff --git a/.changelog/814.txt b/.changelog/814.txt new file mode 100644 index 000000000..efeeb21c7 --- /dev/null +++ b/.changelog/814.txt @@ -0,0 +1,3 @@ +```release-note:improvement +CODEOWNERS: Fix vault-secrets resource ownership to @hashicorp/cloud-vault-secrets team. +``` diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8b372e73f..0b7bfa5f9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,11 +18,12 @@ # all Packer resources and clients are owned by the cloud-packer team *packer* @hashicorp/cloud-packer -# all Vault resources and clients are owned by the vault-cloud team -*vault* @hashicorp/vault-cloud - # all Vault Secrets resources and clients are owned by cloud-vault-secrets team *vaultsecrets* @hashicorp/cloud-vault-secrets +*vault_secrets* @hashicorp/cloud-vault-secrets + +# all Vault resources and clients are owned by the vault-cloud team +*vault* @hashicorp/vault-cloud # all Boundary resources and clients are owned by the boundary-cloud team *boundary* @hashicorp/boundary-cloud diff --git a/.github/workflows/test-identity.yml b/.github/workflows/test-identity.yml new file mode 100644 index 000000000..9c38d5a56 --- /dev/null +++ b/.github/workflows/test-identity.yml @@ -0,0 +1,46 @@ +name: Run Identity Tests on Identity Code Changes + +on: + pull_request: + paths: + - 'internal/provider/iam/**' + +jobs: + identity_tests: + name: Run identity specific tests + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Set up Go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + cache: true + go-version-file: 'go.mod' + cache-dependency-path: go.sum + id: go + + - name: Run 'go mod tidy' and check for differences + run: | + make depscheck + + - name: Get dependencies + run: | + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.3 + go mod download + + - name: Build + run: | + go build -v . + + - name: Run identity tests + env: + HCP_API_HOST: ${{ secrets.HCP_API_HOST }} + HCP_AUTH_URL: ${{ secrets.HCP_AUTH_URL }} + HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }} + HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }} + HCP_ORGANIZATION_ID: ${{ secrets.HCP_ORGANIZATION_ID }} + HCP_PROJECT_ID: ${{ secrets.HCP_PROJECT_ID }} + run: | + make testacc-ci TEST=./internal/provider/iam diff --git a/contributing/writing-tests.md b/contributing/writing-tests.md index 0b92a855e..b5dc6866c 100644 --- a/contributing/writing-tests.md +++ b/contributing/writing-tests.md @@ -62,6 +62,21 @@ PASS ok github.com/hashicorp/terraform-provider-hcp/internal/provider 539.112s ``` +Running a specific test package: +```sh +$ make testacc TEST="./internal/provider/iam" +==> Checking that code complies with gofmt requirements... +golangci-lint run --config ./golangci-config.yml +TF_ACC=1 go test ./internal/provider/iam -v -timeout 360m -parallel=10 +=== RUN TestAccGroupDataSource +--- PASS: TestAccGroupDataSource (8.69s) +... +=== RUN TestAccWorkloadIdentityProviderResource +--- PASS: TestAccWorkloadIdentityProviderResource (11.67s) +PASS +ok github.com/hashicorp/terraform-provider-hcp/internal/provider/iam 125.260s +``` + Entire resource test suites can be targeted by using the naming convention to write the regular expression. diff --git a/internal/clients/vault_secrets.go b/internal/clients/vault_secrets.go index ba372b806..c23804b5d 100644 --- a/internal/clients/vault_secrets.go +++ b/internal/clients/vault_secrets.go @@ -5,8 +5,14 @@ package clients import ( "context" + "fmt" + "net/http" + "regexp" + "strconv" + "time" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/client/secret_service" secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/models" @@ -118,7 +124,6 @@ func CreateVaultSecretsAppSecret(ctx context.Context, client *Client, loc *share // OpenVaultSecretsAppSecret will retrieve the latest secret for a Vault Secrets app, including it's value. func OpenVaultSecretsAppSecret(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName, secretName string) (*secretmodels.Secrets20230613OpenSecret, error) { - getParams := secret_service.NewOpenAppSecretParams() getParams.Context = ctx getParams.AppName = appName @@ -126,11 +131,26 @@ func OpenVaultSecretsAppSecret(ctx context.Context, client *Client, loc *sharedm getParams.LocationOrganizationID = loc.OrganizationID getParams.LocationProjectID = loc.ProjectID - getResp, err := client.VaultSecrets.OpenAppSecret(getParams, nil) - if err != nil { - return nil, err + var getResp *secret_service.OpenAppSecretOK + var err error + for attempt := 0; attempt < retryCount; attempt++ { + getResp, err = client.VaultSecrets.OpenAppSecret(getParams, nil) + if err != nil { + serviceErr, ok := err.(*secret_service.OpenAppSecretDefault) + if !ok { + return nil, err + } + + if shouldRetryErrorCode(serviceErr.Code(), []int{http.StatusTooManyRequests}) { + backOffDuration := getAPIBackoffDuration(serviceErr) + tflog.Debug(ctx, fmt.Sprintf("The api rate limit has been exceeded, retrying in %d seconds, attempt: %d", int64(backOffDuration.Seconds()), (attempt+1))) + time.Sleep(backOffDuration) + continue + } + return nil, err + } + break } - return getResp.Payload.Secret, nil } @@ -151,3 +171,16 @@ func DeleteVaultSecretsAppSecret(ctx context.Context, client *Client, loc *share return nil } + +func getAPIBackoffDuration(serviceErr *secret_service.OpenAppSecretDefault) time.Duration { + re := regexp.MustCompile(`try again in (\d+) seconds`) + match := re.FindStringSubmatch(serviceErr.Error()) + backoffSeconds := 60 + if len(match) > 1 { + backoffSecondsOverride, err := strconv.Atoi(match[1]) + if err == nil { + backoffSeconds = backoffSecondsOverride + } + } + return time.Duration(backoffSeconds) * time.Second +} diff --git a/internal/provider/iam/data_group.go b/internal/provider/iam/data_group.go index f71535a15..43f011a88 100644 --- a/internal/provider/iam/data_group.go +++ b/internal/provider/iam/data_group.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package iam import ( diff --git a/internal/provider/iam/data_group_test.go b/internal/provider/iam/data_group_test.go index 3693e1603..302a28064 100644 --- a/internal/provider/iam/data_group_test.go +++ b/internal/provider/iam/data_group_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package iam_test import ( diff --git a/internal/provider/iam/resource_group.go b/internal/provider/iam/resource_group.go index a235ce1aa..121e625ab 100644 --- a/internal/provider/iam/resource_group.go +++ b/internal/provider/iam/resource_group.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package iam import ( diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go index 17a08baf4..3926a6fe6 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go @@ -28,7 +28,7 @@ func TestAccVaultSecretsResourceSecret(t *testing.T) { }`, testAppName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "app_name", testAppName), - resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_name", "a_long_and_complicated_secret_name_but_less_than_64_characters"), + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_name", "test_secret"), resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_value", "super secret"), ), }, diff --git a/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go b/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go index 9cc530ddd..03096bad0 100644 --- a/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go +++ b/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package waypoint_test import ( @@ -130,12 +133,12 @@ resource "hcp_waypoint_add_on_definition" "test" { summary = "some summary for fun" description = "some description for fun" terraform_no_code_module = { - source = "some source" - version = "some version" + source = "private/waypoint-tfc-testing/waypoint-template-starter/null" + version = "0.0.2" } terraform_cloud_workspace_details = { - name = "some name" - terraform_project_id = "some id" + name = "Default Project" + terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } }`, name) } diff --git a/internal/provider/waypoint/resource_waypoint_add_on_test.go b/internal/provider/waypoint/resource_waypoint_add_on_test.go index 9cd88880e..0a87e99ba 100644 --- a/internal/provider/waypoint/resource_waypoint_add_on_test.go +++ b/internal/provider/waypoint/resource_waypoint_add_on_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package waypoint_test import ( @@ -61,7 +64,7 @@ func testAccCheckWaypointAddOnExists(t *testing.T, resourceName string, addOnMod client := acctest.HCPClients(t) // Get the project ID and ID from state projectID := rs.Primary.Attributes["project_id"] - appTempID := rs.Primary.Attributes["id"] + addOnID := rs.Primary.Attributes["id"] orgID := client.Config.OrganizationID loc := &sharedmodels.HashicorpCloudLocationLocation{ @@ -70,7 +73,7 @@ func testAccCheckWaypointAddOnExists(t *testing.T, resourceName string, addOnMod } // Fetch the add-on - addOn, err := clients.GetAddOnByID(context.Background(), client, loc, appTempID) + addOn, err := clients.GetAddOnByID(context.Background(), client, loc, addOnID) if err != nil { return err } @@ -99,7 +102,7 @@ func testAccCheckWaypointAddOnDestroy(t *testing.T, addOnModel *waypoint.AddOnRe addOn, err := clients.GetAddOnByID(context.Background(), client, loc, id) if err != nil { // expected (500 because the application is destroyed as well) - if clients.IsResponseCodeInternalError(err) { + if clients.IsResponseCodeNotFound(err) { return nil } return err @@ -159,4 +162,4 @@ resource "hcp_waypoint_add_on" "test" { application_id = hcp_waypoint_application.test.id definition_id = hcp_waypoint_add_on_definition.test.id }`, templateName, appName, defName, addOnName) -} +} \ No newline at end of file diff --git a/internal/provider/waypoint/resource_waypoint_application_template_test.go b/internal/provider/waypoint/resource_waypoint_application_template_test.go index 9b1710fc9..2707bd281 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_template_test.go @@ -132,16 +132,16 @@ func testAccCheckWaypointAppTemplateDestroy(t *testing.T, appTemplateModel *wayp func testAppTemplateConfig(name string) string { return fmt.Sprintf(` resource "hcp_waypoint_application_template" "test" { - name = "%s" - summary = "some summary for fun" + name = "%s" + summary = "some summary for fun" readme_markdown_template = base64encode("# Some Readme") terraform_no_code_module = { - source = "some source" - version = "some version" + source = "private/waypoint-tfc-testing/waypoint-template-starter/null" + version = "0.0.2" } terraform_cloud_workspace_details = { - name = "some name" - terraform_project_id = "some id" + name = "Default Project" + terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } labels = ["one", "two"] }`, name)