diff --git a/go.mod b/go.mod index c905d1918..e6df9d6a7 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,14 @@ go 1.15 require ( github.com/armon/go-radix v1.0.0 // indirect github.com/aws/aws-sdk-go v1.37.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible github.com/go-openapi/runtime v0.21.0 github.com/go-openapi/strfmt v0.21.1 github.com/google/uuid v1.3.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/hcl/v2 v2.8.2 // indirect - github.com/hashicorp/hcp-sdk-go v0.14.0 + github.com/hashicorp/hcp-sdk-go v0.16.0 github.com/hashicorp/terraform-plugin-docs v0.5.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0 github.com/posener/complete v1.2.1 // indirect diff --git a/go.sum b/go.sum index cf1d7f370..032ac3e18 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= @@ -373,8 +375,8 @@ github.com/hashicorp/hc-install v0.3.1/go.mod h1:3LCdWcCDS1gaHC9mhHCGbkYfoY6vdsK github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= -github.com/hashicorp/hcp-sdk-go v0.14.0 h1:0+elHACiRu4L+rR+1k9khlu7zaGYq9e/8gy4N4pz0NU= -github.com/hashicorp/hcp-sdk-go v0.14.0/go.mod h1:z0I0eZ+TVJJ7pycnCzMM/ouOw5D5Qnp/zylNXkqGEX0= +github.com/hashicorp/hcp-sdk-go v0.16.0 h1:/UfRdiI1Z2AJGBi24aFO8MeNTWBa08EHyAvH1C9BWw8= +github.com/hashicorp/hcp-sdk-go v0.16.0/go.mod h1:z0I0eZ+TVJJ7pycnCzMM/ouOw5D5Qnp/zylNXkqGEX0= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.15.0 h1:cqjh4d8HYNQrDoEmlSGelHmg2DYDh5yayckvJ5bV18E= diff --git a/internal/clients/client.go b/internal/clients/client.go index 1cd4030e9..eb9a5fdd4 100644 --- a/internal/clients/client.go +++ b/internal/clients/client.go @@ -17,8 +17,8 @@ import ( cloud_consul "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-service/preview/2021-02-04/client" "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-service/preview/2021-02-04/client/consul_service" - cloud_vault "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/client" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/client/vault_service" + cloud_vault "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/client" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/client/vault_service" cloud_packer "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/client" "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/client/packer_service" diff --git a/internal/clients/vault_cluster.go b/internal/clients/vault_cluster.go index 2cfb226b8..d53852d3e 100644 --- a/internal/clients/vault_cluster.go +++ b/internal/clients/vault_cluster.go @@ -4,8 +4,8 @@ import ( "context" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/client/vault_service" - vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/models" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/client/vault_service" + vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/models" ) // GetVaultClusterByID gets an Vault cluster by its ID. diff --git a/internal/provider/data_source_packer_image.go b/internal/provider/data_source_packer_image.go index bd801d8ad..2f938f283 100644 --- a/internal/provider/data_source_packer_image.go +++ b/internal/provider/data_source_packer_image.go @@ -111,6 +111,12 @@ func dataSourcePackerImageRead(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } + if !time.Time(iteration.RevokeAt).IsZero() { + // If RevokeAt is not a zero date, it means this iteration is revoked and should not be used + // to build new images. + return diag.Errorf("the iteration %s is revoked and can not be used. A valid iteration should be used instead.", iteration.ID) + } + found := false for _, build := range iteration.Builds { if build.CloudProvider != cloudProvider { diff --git a/internal/provider/data_source_packer_image_iteration.go b/internal/provider/data_source_packer_image_iteration.go index 96e49386c..e1a6963e1 100644 --- a/internal/provider/data_source_packer_image_iteration.go +++ b/internal/provider/data_source_packer_image_iteration.go @@ -3,6 +3,7 @@ package provider import ( "context" "log" + "time" packermodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/models" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" @@ -161,13 +162,20 @@ func dataSourcePackerImageIterationRead(ctx context.Context, d *schema.ResourceD return diag.FromErr(err) } - if channel.Pointer == nil { + if channel.Iteration == nil { return diag.Errorf("no iteration information found for the specified channel %s", channelSlug) } - iteration := channel.Pointer.Iteration + iteration := channel.Iteration - d.SetId(channel.Pointer.Iteration.ID) + if !time.Time(iteration.RevokeAt).IsZero() { + // If RevokeAt is not a zero date, it means this iteration is revoked and should not be used + // to build new images. + return diag.Errorf("the iteration %s assigned to channel %s is revoked and can not be used. A valid iteration"+ + " must be assigned to this channel before proceeding", iteration.ID, channelSlug) + } + + d.SetId(iteration.ID) if err := d.Set("incremental_version", iteration.IncrementalVersion); err != nil { return diag.FromErr(err) diff --git a/internal/provider/data_source_packer_image_iteration_test.go b/internal/provider/data_source_packer_image_iteration_test.go index fc0a7e18f..119a84d8b 100644 --- a/internal/provider/data_source_packer_image_iteration_test.go +++ b/internal/provider/data_source_packer_image_iteration_test.go @@ -2,10 +2,15 @@ package provider import ( "fmt" + "math/rand" "net/http" + "regexp" "testing" + "time" + "github.com/cenkalti/backoff" "github.com/google/uuid" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-operation/preview/2020-05-05/client/operation_service" "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/client/packer_service" "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/models" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" @@ -16,8 +21,9 @@ import ( ) const ( - acctestBucket = "alpine-acctest" - acctestChannel = "production" + acctestAlpineBucket = "alpine-acctest" + acctestUbuntuBucket = "ubuntu-acctest" + acctestProductionChannel = "production" ) var ( @@ -25,9 +31,96 @@ var ( data "hcp_packer_image_iteration" "alpine" { bucket_name = %q channel = %q - }`, acctestBucket, acctestChannel) + }`, acctestAlpineBucket, acctestProductionChannel) + testAccPackerUbuntuProductionImage = fmt.Sprintf(` + data "hcp_packer_image_iteration" "ubuntu" { + bucket_name = %q + channel = %q + }`, acctestUbuntuBucket, acctestProductionChannel) ) +func upsertRegistry(t *testing.T) { + t.Helper() + + client := testAccProvider.Meta().(*clients.Client) + loc := &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: client.Config.OrganizationID, + ProjectID: client.Config.ProjectID, + } + + params := packer_service.NewPackerServiceCreateRegistryParams() + params.LocationOrganizationID = loc.OrganizationID + params.LocationProjectID = loc.ProjectID + params.Body = &models.HashicorpCloudPackerCreateRegistryRequest{ + FeatureTier: models.HashicorpCloudPackerRegistryConfigTierSTANDARD, + } + + resp, err := client.Packer.PackerServiceCreateRegistry(params, nil) + if err, ok := err.(*packer_service.PackerServiceCreateRegistryDefault); ok { + switch err.Code() { + case int(codes.AlreadyExists), http.StatusConflict: + // all good here ! + return + default: + t.Errorf("unexpected CreateRegistry error, expected nil or 409. Got code: %d err: %v", err.Code(), err) + return + } + } + + waitForOperation(t, loc, "Create Registry", resp.Payload.Operation.ID, client) + return +} + +func waitForOperation( + t *testing.T, + loc *sharedmodels.HashicorpCloudLocationLocation, + operationName string, + operationID string, + client *clients.Client, +) { + timeout := "5s" + params := operation_service.NewWaitParams() + params.ID = operationID + params.Timeout = &timeout + params.LocationOrganizationID = loc.OrganizationID + params.LocationProjectID = loc.ProjectID + + operation := func() error { + resp, err := client.Operation.Wait(params, nil) + if err != nil { + t.Errorf("unexpected error %#v", err) + } + + if resp.Payload.Operation.Error != nil { + t.Errorf("Operation failed: %s", resp.Payload.Operation.Error.Message) + } + + switch resp.Payload.Operation.State { + case "PENDING": + msg := fmt.Sprintf("==> Operation \"%s\" pending...", operationName) + return fmt.Errorf(msg) + case "RUNNING": + msg := fmt.Sprintf("==> Operation \"%s\" running...", operationName) + return fmt.Errorf(msg) + case "DONE": + default: + t.Errorf("Operation returned unknown state: %s", resp.Payload.Operation.State) + } + return nil + } + + bo := backoff.NewExponentialBackOff() + bo.InitialInterval = 10 * time.Second + bo.RandomizationFactor = 0.5 + bo.Multiplier = 1.5 + bo.MaxInterval = 30 * time.Second + bo.MaxElapsedTime = 40 * time.Minute + err := backoff.Retry(operation, bo) + if err != nil { + t.Errorf("unexpected error: %#v", err) + } +} + func upsertBucket(t *testing.T, bucketSlug string) { t.Helper() @@ -92,7 +185,30 @@ func upsertIteration(t *testing.T, bucketSlug, fingerprint string) { t.Errorf("unexpected CreateIteration error, expected nil or 409. Got %v", err) } -func getIterationIDFromFingerPrint(t *testing.T, bucketSlug, fingerprint string) string { +func revokeIteration(t *testing.T, iterationID, bucketSlug, revokeIn string) { + t.Helper() + client := testAccProvider.Meta().(*clients.Client) + loc := &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: client.Config.OrganizationID, + ProjectID: client.Config.ProjectID, + } + + params := packer_service.NewPackerServiceUpdateIterationParams() + params.LocationOrganizationID = loc.OrganizationID + params.LocationProjectID = loc.ProjectID + params.IterationID = iterationID + params.Body = &models.HashicorpCloudPackerUpdateIterationRequest{ + BucketSlug: bucketSlug, + RevokeIn: revokeIn, + } + + _, err := client.Packer.PackerServiceUpdateIteration(params, nil) + if err != nil { + t.Fatal(err) + } +} + +func getIterationIDFromFingerPrint(t *testing.T, bucketSlug string, fingerprint string) (string, error) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -109,9 +225,9 @@ func getIterationIDFromFingerPrint(t *testing.T, bucketSlug, fingerprint string) ok, err := client.Packer.PackerServiceGetIteration(getItParams, nil) if err != nil { - t.Fatal(err) + return "", err } - return ok.Payload.Iteration.ID + return ok.Payload.Iteration.ID, nil } func upsertBuild(t *testing.T, bucketSlug, fingerprint, iterationID string) { @@ -183,7 +299,7 @@ func upsertBuild(t *testing.T, bucketSlug, fingerprint, iterationID string) { } } -func createChannel(t *testing.T, bucketSlug, channelSlug string) { +func createChannel(t *testing.T, bucketSlug, channelSlug, iterationID string) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -197,8 +313,8 @@ func createChannel(t *testing.T, bucketSlug, channelSlug string) { createChParams.LocationProjectID = loc.ProjectID createChParams.BucketSlug = bucketSlug createChParams.Body = &models.HashicorpCloudPackerCreateChannelRequest{ - Slug: channelSlug, - IncrementalVersion: 1, + Slug: channelSlug, + IterationID: iterationID, } _, err := client.Packer.PackerServiceCreateChannel(createChParams, nil) @@ -207,16 +323,16 @@ func createChannel(t *testing.T, bucketSlug, channelSlug string) { } if err, ok := err.(*packer_service.PackerServiceCreateChannelDefault); ok { switch err.Code() { - case int(codes.Aborted), http.StatusConflict: + case int(codes.AlreadyExists), http.StatusConflict: // all good here ! - updateChannel(t, bucketSlug, channelSlug) + updateChannel(t, bucketSlug, channelSlug, iterationID) return } } t.Errorf("unexpected CreateChannel error, expected nil. Got %v", err) } -func updateChannel(t *testing.T, bucketSlug, channelSlug string) { +func updateChannel(t *testing.T, bucketSlug, channelSlug, iterationID string) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -231,7 +347,7 @@ func updateChannel(t *testing.T, bucketSlug, channelSlug string) { updateChParams.BucketSlug = bucketSlug updateChParams.Slug = channelSlug updateChParams.Body = &models.HashicorpCloudPackerUpdateChannelRequest{ - IncrementalVersion: 1, + IterationID: iterationID, } _, err := client.Packer.PackerServiceUpdateChannel(updateChParams, nil) @@ -241,7 +357,7 @@ func updateChannel(t *testing.T, bucketSlug, channelSlug string) { t.Errorf("unexpected UpdateChannel error, expected nil. Got %v", err) } -func deleteBucket(t *testing.T, bucketSlug string) { +func deleteBucket(t *testing.T, bucketSlug string, logOnError bool) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -259,10 +375,12 @@ func deleteBucket(t *testing.T, bucketSlug string) { if err == nil { return } - t.Errorf("unexpected DeleteBucket error, expected nil. Got %v", err) + if logOnError { + t.Logf("unexpected DeleteBucket error, expected nil. Got %v", err) + } } -func deleteIteration(t *testing.T, bucketSlug string, iterationID string) { +func deleteIteration(t *testing.T, bucketSlug string, iterationFingerprint string, logOnError bool) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -271,20 +389,30 @@ func deleteIteration(t *testing.T, bucketSlug string, iterationID string) { ProjectID: client.Config.ProjectID, } + iterationID, err := getIterationIDFromFingerPrint(t, acctestIterationUbuntuBucket, iterationFingerprint) + if err != nil { + if logOnError { + t.Logf(err.Error()) + } + return + } + deleteItParams := packer_service.NewPackerServiceDeleteIterationParams() deleteItParams.LocationOrganizationID = loc.OrganizationID deleteItParams.LocationProjectID = loc.ProjectID deleteItParams.BucketSlug = &bucketSlug deleteItParams.IterationID = iterationID - _, err := client.Packer.PackerServiceDeleteIteration(deleteItParams, nil) + _, err = client.Packer.PackerServiceDeleteIteration(deleteItParams, nil) if err == nil { return } - t.Errorf("unexpected DeleteIteration error, expected nil. Got %v", err) + if logOnError { + t.Logf("unexpected DeleteIteration error, expected nil. Got %v", err) + } } -func deleteChannel(t *testing.T, bucketSlug string, channelSlug string) { +func deleteChannel(t *testing.T, bucketSlug string, channelSlug string, logOnError bool) { t.Helper() client := testAccProvider.Meta().(*clients.Client) @@ -303,7 +431,9 @@ func deleteChannel(t *testing.T, bucketSlug string, channelSlug string) { if err == nil { return } - t.Errorf("unexpected DeleteChannel error, expected nil. Got %v", err) + if logOnError { + t.Logf("unexpected DeleteChannel error, expected nil. Got %v", err) + } } func TestAcc_dataSourcePacker(t *testing.T) { @@ -314,24 +444,26 @@ func TestAcc_dataSourcePacker(t *testing.T) { PreCheck: func() { testAccPreCheck(t, false) }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { - itID := getIterationIDFromFingerPrint(t, acctestBucket, fingerprint) - deleteChannel(t, acctestBucket, acctestChannel) - deleteIteration(t, acctestBucket, itID) - deleteBucket(t, acctestBucket) + deleteChannel(t, acctestAlpineBucket, acctestProductionChannel, false) + deleteIteration(t, acctestAlpineBucket, fingerprint, false) + deleteBucket(t, acctestAlpineBucket, false) return nil }, Steps: []resource.TestStep{ // testing that getting the production channel of the alpine image // works. - { PreConfig: func() { - upsertBucket(t, acctestBucket) - upsertIteration(t, acctestBucket, fingerprint) - itID := getIterationIDFromFingerPrint(t, acctestBucket, fingerprint) - upsertBuild(t, acctestBucket, fingerprint, itID) - createChannel(t, acctestBucket, acctestChannel) + upsertRegistry(t) + upsertBucket(t, acctestAlpineBucket) + upsertIteration(t, acctestAlpineBucket, fingerprint) + itID, err := getIterationIDFromFingerPrint(t, acctestAlpineBucket, fingerprint) + if err != nil { + t.Fatal(err.Error()) + } + upsertBuild(t, acctestAlpineBucket, fingerprint, itID) + createChannel(t, acctestAlpineBucket, acctestProductionChannel, itID) }, Config: testConfig(testAccPackerAlpineProductionImage), Check: resource.ComposeTestCheckFunc( @@ -342,3 +474,42 @@ func TestAcc_dataSourcePacker(t *testing.T) { }, }) } + +func TestAcc_dataSourcePacker_revokedIteration(t *testing.T) { + fingerprint := fmt.Sprintf("%d", rand.Int()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t, false) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + // testing that getting a revoked iteration fails properly + { + PreConfig: func() { + // CheckDestroy doesn't get called when the test fails and doesn't + // produce any tf state. In this case we destroy any existing resource + // before creating them. + deleteChannel(t, acctestUbuntuBucket, acctestProductionChannel, false) + deleteIteration(t, acctestUbuntuBucket, fingerprint, false) + deleteBucket(t, acctestUbuntuBucket, false) + + upsertRegistry(t) + upsertBucket(t, acctestUbuntuBucket) + upsertIteration(t, acctestUbuntuBucket, fingerprint) + itID, err := getIterationIDFromFingerPrint(t, acctestUbuntuBucket, fingerprint) + if err != nil { + t.Fatal(err.Error()) + } + upsertBuild(t, acctestUbuntuBucket, fingerprint, itID) + createChannel(t, acctestUbuntuBucket, acctestProductionChannel, itID) + // Schedule revocation to the future, otherwise we won't be able to revoke an iteration that + // it's assigned to a channel + revokeIteration(t, itID, acctestUbuntuBucket, "5s") + // Sleep to make sure the iteration is revoked when we test + time.Sleep(5 * time.Second) + }, + Config: testConfig(testAccPackerUbuntuProductionImage), + ExpectError: regexp.MustCompile(`Error: the iteration (\d|\w){26} assigned to channel (\w|\W)* is revoked and can not be used. A valid iteration must be assigned to this channel before proceeding`), + }, + }, + }) +} diff --git a/internal/provider/data_source_packer_image_test.go b/internal/provider/data_source_packer_image_test.go index 71b449d18..19763d6d4 100644 --- a/internal/provider/data_source_packer_image_test.go +++ b/internal/provider/data_source_packer_image_test.go @@ -36,10 +36,9 @@ func TestAcc_dataSourcePackerImage(t *testing.T) { PreCheck: func() { testAccPreCheck(t, false) }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { - itID := getIterationIDFromFingerPrint(t, acctestImageBucket, fingerprint) - deleteChannel(t, acctestImageBucket, acctestImageChannel) - deleteIteration(t, acctestImageBucket, itID) - deleteBucket(t, acctestImageBucket) + deleteChannel(t, acctestImageBucket, acctestImageChannel, false) + deleteIteration(t, acctestImageBucket, fingerprint, false) + deleteBucket(t, acctestImageBucket, false) return nil }, PreventPostDestroyRefresh: true, @@ -50,9 +49,12 @@ func TestAcc_dataSourcePackerImage(t *testing.T) { PreConfig: func() { upsertBucket(t, acctestImageBucket) upsertIteration(t, acctestImageBucket, fingerprint) - itID := getIterationIDFromFingerPrint(t, acctestImageBucket, fingerprint) + itID, err := getIterationIDFromFingerPrint(t, acctestImageBucket, fingerprint) + if err != nil { + t.Fatal(err.Error()) + } upsertBuild(t, acctestImageBucket, fingerprint, itID) - createChannel(t, acctestImageBucket, acctestImageChannel) + createChannel(t, acctestImageBucket, acctestImageChannel, itID) }, Config: testAccPackerImageAlpineProduction, Check: resource.ComposeTestCheckFunc( diff --git a/internal/provider/data_source_packer_iteration.go b/internal/provider/data_source_packer_iteration.go index 52bd8cbd8..3a736f5fb 100644 --- a/internal/provider/data_source_packer_iteration.go +++ b/internal/provider/data_source_packer_iteration.go @@ -3,6 +3,7 @@ package provider import ( "context" "log" + "time" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -98,13 +99,19 @@ func dataSourcePackerIterationRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - if channel.Pointer == nil { + if channel.Iteration == nil { return diag.Errorf("no iteration information found for the specified channel %s", channelSlug) } - iteration := channel.Pointer.Iteration + iteration := channel.Iteration + if !time.Time(iteration.RevokeAt).IsZero() { + // If RevokeAt is not a zero date, it means this iteration is revoked and should not be used + // to build new images. + return diag.Errorf("the iteration %s assigned to channel %s is revoked and can not be used. A valid iteration"+ + " must be assigned to this channel before proceeding", iteration.ID, channelSlug) + } - d.SetId(channel.Pointer.Iteration.ID) + d.SetId(iteration.ID) if err := d.Set("author_id", iteration.AuthorID); err != nil { return diag.FromErr(err) diff --git a/internal/provider/data_source_packer_iteration_test.go b/internal/provider/data_source_packer_iteration_test.go index 47e051ff1..20eea2e78 100644 --- a/internal/provider/data_source_packer_iteration_test.go +++ b/internal/provider/data_source_packer_iteration_test.go @@ -2,15 +2,19 @@ package provider import ( "fmt" + "math/rand" + "regexp" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( - acctestIterationBucket = "alpine-acctest-itertest" - acctestIterationChannel = "production-iter-test" + acctestIterationBucket = "alpine-acctest-itertest" + acctestIterationUbuntuBucket = "ubuntu-acctest-itertest" + acctestIterationChannel = "production-iter-test" ) var ( @@ -19,6 +23,11 @@ var ( bucket_name = %q channel = %q }`, acctestIterationBucket, acctestIterationChannel) + testAccPackerIterationUbuntuProduction = fmt.Sprintf(` + data "hcp_packer_iteration" "ubuntu" { + bucket_name = %q + channel = %q + }`, acctestIterationUbuntuBucket, acctestIterationChannel) ) func TestAcc_dataSourcePackerIteration(t *testing.T) { @@ -29,10 +38,9 @@ func TestAcc_dataSourcePackerIteration(t *testing.T) { PreCheck: func() { testAccPreCheck(t, false) }, ProviderFactories: providerFactories, CheckDestroy: func(*terraform.State) error { - itID := getIterationIDFromFingerPrint(t, acctestIterationBucket, fingerprint) - deleteChannel(t, acctestIterationBucket, acctestIterationChannel) - deleteIteration(t, acctestIterationBucket, itID) - deleteBucket(t, acctestIterationBucket) + deleteChannel(t, acctestIterationBucket, acctestIterationChannel, false) + deleteIteration(t, acctestIterationBucket, fingerprint, false) + deleteBucket(t, acctestIterationBucket, false) return nil }, @@ -43,9 +51,12 @@ func TestAcc_dataSourcePackerIteration(t *testing.T) { PreConfig: func() { upsertBucket(t, acctestIterationBucket) upsertIteration(t, acctestIterationBucket, fingerprint) - itID := getIterationIDFromFingerPrint(t, acctestIterationBucket, fingerprint) + itID, err := getIterationIDFromFingerPrint(t, acctestIterationBucket, fingerprint) + if err != nil { + t.Fatal(err.Error()) + } upsertBuild(t, acctestIterationBucket, fingerprint, itID) - createChannel(t, acctestIterationBucket, acctestIterationChannel) + createChannel(t, acctestIterationBucket, acctestIterationChannel, itID) }, Config: testConfig(testAccPackerIterationAlpineProduction), Check: resource.ComposeTestCheckFunc( @@ -56,3 +67,49 @@ func TestAcc_dataSourcePackerIteration(t *testing.T) { }, }) } + +func TestAcc_dataSourcePackerIteration_revokedIteration(t *testing.T) { + fingerprint := fmt.Sprintf("%d", rand.Int()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t, false) }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + deleteChannel(t, acctestIterationUbuntuBucket, acctestIterationChannel, false) + deleteIteration(t, acctestIterationUbuntuBucket, fingerprint, false) + deleteBucket(t, acctestIterationUbuntuBucket, false) + return nil + }, + + Steps: []resource.TestStep{ + // testing that getting the production channel of the alpine image + // works. + { + PreConfig: func() { + // CheckDestroy doesn't get called when the test fails and doesn't + // produce any tf state. In this case we destroy any existing resource + // before creating them. + deleteChannel(t, acctestIterationUbuntuBucket, acctestIterationChannel, false) + deleteIteration(t, acctestIterationUbuntuBucket, fingerprint, false) + deleteBucket(t, acctestIterationUbuntuBucket, false) + + upsertBucket(t, acctestIterationUbuntuBucket) + upsertIteration(t, acctestIterationUbuntuBucket, fingerprint) + itID, err := getIterationIDFromFingerPrint(t, acctestIterationUbuntuBucket, fingerprint) + if err != nil { + t.Fatal(err.Error()) + } + upsertBuild(t, acctestIterationUbuntuBucket, fingerprint, itID) + createChannel(t, acctestIterationUbuntuBucket, acctestIterationChannel, itID) + // Schedule revocation to the future, otherwise we won't be able to revoke an iteration that + // it's assigned to a channel + revokeIteration(t, itID, acctestIterationUbuntuBucket, "5s") + // Sleep to make sure the iteration is revoked when we test + time.Sleep(5 * time.Second) + }, + Config: testConfig(testAccPackerIterationUbuntuProduction), + ExpectError: regexp.MustCompile(`Error: the iteration (\d|\w){26} assigned to channel (\w|\W)* is revoked and can not be used. A valid iteration must be assigned to this channel before proceeding`), + }, + }, + }) +} diff --git a/internal/provider/resource_vault_cluster.go b/internal/provider/resource_vault_cluster.go index f2c356a81..a5d45e0b4 100644 --- a/internal/provider/resource_vault_cluster.go +++ b/internal/provider/resource_vault_cluster.go @@ -8,7 +8,7 @@ import ( "time" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" - vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/models" + vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 3c7dc979d..98037ff84 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -9,7 +9,7 @@ import ( "github.com/go-openapi/strfmt" "github.com/hashicorp/go-cty/cty" consulmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-consul-service/preview/2021-02-04/models" - vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/preview/2020-11-25/models" + vaultmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index 0b40154f2..60ac37161 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -376,8 +376,8 @@ func Test_validateVaultClusterTier(t *testing.T) { expected: diag.Diagnostics{ diag.Diagnostic{ Severity: diag.Error, - Summary: "expected 'development' to be one of: [dev standard_small standard_medium standard_large starter_small]", - Detail: "expected 'development' to be one of: [dev standard_small standard_medium standard_large starter_small] (value is case-insensitive).", + Summary: "expected 'development' to be one of: [dev standard_small standard_medium standard_large starter_small plus_small plus_medium plus_large]", + Detail: "expected 'development' to be one of: [dev standard_small standard_medium standard_large starter_small plus_small plus_medium plus_large] (value is case-insensitive).", AttributePath: nil, }, },