diff --git a/command/build.go b/command/build.go index c9f7a537d1c..7d231a5b7b3 100644 --- a/command/build.go +++ b/command/build.go @@ -105,13 +105,13 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int return ret } - defer hcpRegistry.IterationStatusSummary() + defer hcpRegistry.VersionStatusSummary() - err := hcpRegistry.PopulateIteration(buildCtx) + err := hcpRegistry.PopulateVersion(buildCtx) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{ &hcl.Diagnostic{ - Summary: "HCP: populating iteration failed", + Summary: "HCP: populating version failed", Severity: hcl.DiagError, Detail: err.Error(), }, diff --git a/command/plugin.go b/command/plugin.go index 50699654918..6cb92b86818 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -15,8 +15,10 @@ import ( filebuilder "github.com/hashicorp/packer/builder/file" nullbuilder "github.com/hashicorp/packer/builder/null" + hcppackerartifactdatasource "github.com/hashicorp/packer/datasource/hcp-packer-artifact" hcppackerimagedatasource "github.com/hashicorp/packer/datasource/hcp-packer-image" hcppackeriterationdatasource "github.com/hashicorp/packer/datasource/hcp-packer-iteration" + hcppackerversiondatasource "github.com/hashicorp/packer/datasource/hcp-packer-version" httpdatasource "github.com/hashicorp/packer/datasource/http" nulldatasource "github.com/hashicorp/packer/datasource/null" artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice" @@ -63,8 +65,10 @@ var PostProcessors = map[string]packersdk.PostProcessor{ } var Datasources = map[string]packersdk.Datasource{ + "hcp-packer-artifact": new(hcppackerartifactdatasource.Datasource), "hcp-packer-image": new(hcppackerimagedatasource.Datasource), "hcp-packer-iteration": new(hcppackeriterationdatasource.Datasource), + "hcp-packer-version": new(hcppackerversiondatasource.Datasource), "http": new(httpdatasource.Datasource), "null": new(nulldatasource.Datasource), } diff --git a/datasource/hcp-packer-artifact/data.go b/datasource/hcp-packer-artifact/data.go new file mode 100644 index 00000000000..f148c3a35d2 --- /dev/null +++ b/datasource/hcp-packer-artifact/data.go @@ -0,0 +1,251 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:generate packer-sdc struct-markdown +//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config +package hcp_packer_artifact + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/hcl/v2/hcldec" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/hcl2helper" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/config" + hcpapi "github.com/hashicorp/packer/internal/hcp/api" +) + +type Datasource struct { + config Config +} + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + // The name of the bucket your artifact is in. + BucketName string `mapstructure:"bucket_name" required:"true"` + + // The name of the channel to use when retrieving your artifact. + // Either `channel_name` or `version_fingerprint` MUST be set. + // If using several artifacts from a single version, you may prefer sourcing a version first, + // and referencing it for subsequent uses, as every `hcp_packer_artifact` with the channel set will generate a + // potentially billable HCP Packer request, but if several `hcp_packer_artifact`s use a shared `hcp_packer_version` + // that will only generate one potentially billable request. + ChannelName string `mapstructure:"channel_name" required:"true"` + + // The fingerprint of the version to use when retrieving your artifact. + // Either this or `channel_name` MUST be set. + // Mutually exclusive with `channel_name` + VersionFingerprint string `mapstructure:"version_fingerprint" required:"true"` + + // The name of the platform that your artifact is for. + // For example, "aws", "azure", or "gce". + Platform string `mapstructure:"platform" required:"true"` + + // The name of the region your artifact is in. + // For example "us-east-1". + Region string `mapstructure:"region" required:"true"` + + // The specific Packer builder used to create the artifact. + // For example, "amazon-ebs.example" + ComponentType string `mapstructure:"component_type" required:"false"` +} + +func (d *Datasource) ConfigSpec() hcldec.ObjectSpec { + return d.config.FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Configure(raws ...interface{}) error { + err := config.Decode(&d.config, nil, raws...) + if err != nil { + return err + } + + var errs *packersdk.MultiError + + if d.config.BucketName == "" { + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf("the `bucket_name` must be specified"), + ) + } + + // Ensure either channel_name or version_fingerprint is set, and not both at the same time. + if d.config.ChannelName == "" && d.config.VersionFingerprint == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New( + "`version_fingerprint` or `channel_name` must be specified", + )) + } + if d.config.ChannelName != "" && d.config.VersionFingerprint != "" { + errs = packersdk.MultiErrorAppend(errs, errors.New( + "`version_fingerprint` and `channel_name` cannot be specified together", + )) + } + + if d.config.Region == "" { + errs = packersdk.MultiErrorAppend(errs, + fmt.Errorf("the `region` must be specified"), + ) + } + + if d.config.Platform == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf( + "the `platform` must be specified", + )) + } + + if errs != nil && len(errs.Errors) > 0 { + return errs + } + return nil +} + +// DatasourceOutput Information from []*hcpPackerModels.HashicorpCloudPacker20230101Artifact with some information +// from the parent []*hcpPackerModels.HashicorpCloudPacker20230101Build included where it seemed +// like it might be relevant. Need to copy so we can generate +type DatasourceOutput struct { + // The name of the platform that the artifact exists in. + // For example, "aws", "azure", or "gce". + Platform string `mapstructure:"platform"` + + // The specific Packer builder or post-processor used to create the artifact. + ComponentType string `mapstructure:"component_type"` + + // The date and time at which the artifact was created. + CreatedAt string `mapstructure:"created_at"` + + // The ID of the build that created the artifact. This is a ULID, which is a + // unique identifier similar to a UUID. It is created by the HCP Packer + // Registry when a build is first created, and is unique to this build. + BuildID string `mapstructure:"build_id"` + + // The version ID. This is a ULID, which is a unique identifier similar + // to a UUID. It is created by the HCP Packer Registry when a version is + // first created, and is unique to this version. + VersionID string `mapstructure:"version_id"` + + // The ID of the channel used to query the version. This value will be empty if the `version_fingerprint` was used + // directly instead of a channel. + ChannelID string `mapstructure:"channel_id"` + + // The UUID associated with the Packer run that created this artifact. + PackerRunUUID string `mapstructure:"packer_run_uuid"` + + // Identifier or URL of the remote artifact as given by a build. + // For example, ami-12345. + ExternalIdentifier string `mapstructure:"external_identifier"` + + // The region as given by `packer build`. eg. "ap-east-1". + // For locally managed clouds, this may map instead to a cluster, server or datastore. + Region string `mapstructure:"region"` + + // The key:value metadata labels associated with this build. + Labels map[string]string `mapstructure:"labels"` +} + +func (d *Datasource) OutputSpec() hcldec.ObjectSpec { + return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Execute() (cty.Value, error) { + ctx := context.TODO() + + cli, err := hcpapi.NewClient() + if err != nil { + return cty.NullVal(cty.EmptyObject), err + } + + var version *hcpPackerModels.HashicorpCloudPacker20230101Version + var channelID string + if d.config.VersionFingerprint != "" { + log.Printf( + "[INFO] Reading info from HCP Packer Registry (%s) "+ + "[project_id=%s, organization_id=%s, version_fingerprint=%s]", + d.config.BucketName, cli.ProjectID, cli.OrganizationID, d.config.VersionFingerprint, + ) + + version, err = cli.GetVersion(ctx, d.config.BucketName, d.config.VersionFingerprint) + if err != nil { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "error retrieving version from HCP Packer Registry: %s", err, + ) + } + } else { + log.Printf( + "[INFO] Reading info from HCP Packer Registry (%s) "+ + "[project_id=%s, organization_id=%s, channel=%s]", + d.config.BucketName, cli.ProjectID, cli.OrganizationID, d.config.ChannelName, + ) + + var channel *hcpPackerModels.HashicorpCloudPacker20230101Channel + channel, err = cli.GetChannel(ctx, d.config.BucketName, d.config.ChannelName) + if err != nil { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "error retrieving channel from HCP Packer Registry: %s", err.Error(), + ) + } + + if channel.Version == nil { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "there is no version associated with the channel %s", d.config.ChannelName, + ) + } + channelID = channel.ID + version = channel.Version + } + + if *version.Status == hcpPackerModels.HashicorpCloudPacker20230101VersionStatusVERSIONREVOKED { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "the version %s is revoked and can not be used on Packer builds", version.ID, + ) + } + + var output DatasourceOutput + + cloudAndRegions := map[string][]string{} + for _, build := range version.Builds { + if build.Platform != d.config.Platform { + continue + } + for _, artifact := range build.Artifacts { + cloudAndRegions[build.Platform] = append(cloudAndRegions[build.Platform], artifact.Region) + if artifact.Region == d.config.Region && filterBuildByComponentType(build, d.config.ComponentType) { + // This is the desired artifact. + output = DatasourceOutput{ + Platform: build.Platform, + ComponentType: build.ComponentType, + CreatedAt: artifact.CreatedAt.String(), + BuildID: build.ID, + VersionID: build.VersionID, + ChannelID: channelID, + PackerRunUUID: build.PackerRunUUID, + ExternalIdentifier: artifact.ExternalIdentifier, + Region: artifact.Region, + Labels: build.Labels, + } + return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil + } + } + } + + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "could not find a build result matching "+ + "[region=%q, platform=%q, component_type=%q]. Available: %v ", + d.config.Region, d.config.Platform, d.config.ComponentType, cloudAndRegions, + ) +} + +func filterBuildByComponentType(build *hcpPackerModels.HashicorpCloudPacker20230101Build, componentType string) bool { + // optional field is not specified, passthrough + if componentType == "" { + return true + } + // if specified, only the matched artifact metadata is returned by this effect + return build.ComponentType == componentType +} diff --git a/datasource/hcp-packer-artifact/data.hcl2spec.go b/datasource/hcp-packer-artifact/data.hcl2spec.go new file mode 100644 index 00000000000..5c34c556b5d --- /dev/null +++ b/datasource/hcp-packer-artifact/data.hcl2spec.go @@ -0,0 +1,98 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package hcp_packer_artifact + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + BucketName *string `mapstructure:"bucket_name" required:"true" cty:"bucket_name" hcl:"bucket_name"` + ChannelName *string `mapstructure:"channel_name" required:"true" cty:"channel_name" hcl:"channel_name"` + VersionFingerprint *string `mapstructure:"version_fingerprint" required:"true" cty:"version_fingerprint" hcl:"version_fingerprint"` + Platform *string `mapstructure:"platform" required:"true" cty:"platform" hcl:"platform"` + Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` + ComponentType *string `mapstructure:"component_type" required:"false" cty:"component_type" hcl:"component_type"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "bucket_name": &hcldec.AttrSpec{Name: "bucket_name", Type: cty.String, Required: false}, + "channel_name": &hcldec.AttrSpec{Name: "channel_name", Type: cty.String, Required: false}, + "version_fingerprint": &hcldec.AttrSpec{Name: "version_fingerprint", Type: cty.String, Required: false}, + "platform": &hcldec.AttrSpec{Name: "platform", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "component_type": &hcldec.AttrSpec{Name: "component_type", Type: cty.String, Required: false}, + } + return s +} + +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatDatasourceOutput struct { + Platform *string `mapstructure:"platform" cty:"platform" hcl:"platform"` + ComponentType *string `mapstructure:"component_type" cty:"component_type" hcl:"component_type"` + CreatedAt *string `mapstructure:"created_at" cty:"created_at" hcl:"created_at"` + BuildID *string `mapstructure:"build_id" cty:"build_id" hcl:"build_id"` + VersionID *string `mapstructure:"version_id" cty:"version_id" hcl:"version_id"` + ChannelID *string `mapstructure:"channel_id" cty:"channel_id" hcl:"channel_id"` + PackerRunUUID *string `mapstructure:"packer_run_uuid" cty:"packer_run_uuid" hcl:"packer_run_uuid"` + ExternalIdentifier *string `mapstructure:"external_identifier" cty:"external_identifier" hcl:"external_identifier"` + Region *string `mapstructure:"region" cty:"region" hcl:"region"` + Labels map[string]string `mapstructure:"labels" cty:"labels" hcl:"labels"` +} + +// FlatMapstructure returns a new FlatDatasourceOutput. +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*DatasourceOutput) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatDatasourceOutput) +} + +// HCL2Spec returns the hcl spec of a DatasourceOutput. +// This spec is used by HCL to read the fields of DatasourceOutput. +// The decoded values from this spec will then be applied to a FlatDatasourceOutput. +func (*FlatDatasourceOutput) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "platform": &hcldec.AttrSpec{Name: "platform", Type: cty.String, Required: false}, + "component_type": &hcldec.AttrSpec{Name: "component_type", Type: cty.String, Required: false}, + "created_at": &hcldec.AttrSpec{Name: "created_at", Type: cty.String, Required: false}, + "build_id": &hcldec.AttrSpec{Name: "build_id", Type: cty.String, Required: false}, + "version_id": &hcldec.AttrSpec{Name: "version_id", Type: cty.String, Required: false}, + "channel_id": &hcldec.AttrSpec{Name: "channel_id", Type: cty.String, Required: false}, + "packer_run_uuid": &hcldec.AttrSpec{Name: "packer_run_uuid", Type: cty.String, Required: false}, + "external_identifier": &hcldec.AttrSpec{Name: "external_identifier", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "labels": &hcldec.AttrSpec{Name: "labels", Type: cty.Map(cty.String), Required: false}, + } + return s +} diff --git a/datasource/hcp-packer-image/data.go b/datasource/hcp-packer-image/data.go index aba7a6d19bf..6b72366ea1d 100644 --- a/datasource/hcp-packer-image/data.go +++ b/datasource/hcp-packer-image/data.go @@ -15,7 +15,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/hcl/v2/hcldec" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" + hcpPackerDeprecatedModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" "github.com/hashicorp/packer-plugin-sdk/common" "github.com/hashicorp/packer-plugin-sdk/hcl2helper" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -36,9 +36,9 @@ type Config struct { // Mutually exclusive with `iteration_id`. // If using several images from a single iteration, you may prefer // sourcing an iteration first, and referencing it for subsequent uses, - // as every `hcp_packer_image` with the channel set will generate a + // as every `hcp-packer-image` with the channel set will generate a // potentially billable HCP Packer request, but if several - // `hcp_packer_image`s use a shared `hcp_packer_iteration` that will + // `hcp-packer-image`s use a shared `hcp-packer-iteration` that will // only generate one potentially billable request. Channel string `mapstructure:"channel" required:"true"` // The ID of the iteration to use when retrieving your image @@ -101,8 +101,8 @@ func (d *Datasource) Configure(raws ...interface{}) error { return nil } -// DatasourceOutput Information from []*models.HashicorpCloudPackerImage with some information -// from the parent []*models.HashicorpCloudPackerBuild included where it seemed +// DatasourceOutput Information from []*hcpPackerDeprecatedModels.HashicorpCloudPackerImage with some information +// from the parent []*hcpPackerDeprecatedModels.HashicorpCloudPackerBuild included where it seemed // like it might be relevant. Need to copy so we can generate type DatasourceOutput struct { // The name of the cloud provider that the image exists in. For example, @@ -140,14 +140,16 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec { } func (d *Datasource) Execute() (cty.Value, error) { + log.Printf("[WARN] Deprecation: `hcp-packer-image` datasource has been deprecated. " + + "Please use `hcp-packer-artifact` datasource instead.") ctx := context.TODO() - cli, err := hcpapi.NewClient() + cli, err := hcpapi.NewDeprecatedClient() if err != nil { return cty.NullVal(cty.EmptyObject), err } - var iteration *models.HashicorpCloudPackerIteration + var iteration *hcpPackerDeprecatedModels.HashicorpCloudPackerIteration var channelID string if d.config.IterationID != "" { log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, iteration_id=%s]", @@ -219,7 +221,7 @@ func (d *Datasource) Execute() (cty.Value, error) { d.config.Region, d.config.CloudProvider, d.config.ComponentType, cloudAndRegions) } -func filterBuildByComponentType(build *models.HashicorpCloudPackerBuild, componentType string) bool { +func filterBuildByComponentType(build *hcpPackerDeprecatedModels.HashicorpCloudPackerBuild, componentType string) bool { // optional field is not specified, passthrough if componentType == "" { return true diff --git a/datasource/hcp-packer-iteration/data.go b/datasource/hcp-packer-iteration/data.go index ff0bbd46f34..abe92722cfb 100644 --- a/datasource/hcp-packer-iteration/data.go +++ b/datasource/hcp-packer-iteration/data.go @@ -100,9 +100,11 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec { } func (d *Datasource) Execute() (cty.Value, error) { + log.Printf("[WARN] Deprecation: `hcp-packer-iteration` datasource has been deprecated. " + + "Please use `hcp-packer-version` datasource instead.") ctx := context.TODO() - cli, err := hcpapi.NewClient() + cli, err := hcpapi.NewDeprecatedClient() if err != nil { return cty.NullVal(cty.EmptyObject), err } diff --git a/datasource/hcp-packer-iteration/data_acc_test.go b/datasource/hcp-packer-iteration/data_acc_test.go index 82e3b78e5f7..fbb7176768d 100644 --- a/datasource/hcp-packer-iteration/data_acc_test.go +++ b/datasource/hcp-packer-iteration/data_acc_test.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "testing" "github.com/hashicorp/packer-plugin-sdk/acctest" @@ -17,27 +18,42 @@ import ( //go:embed test-fixtures/template.pkr.hcl var testDatasourceBasic string +//go:embed test-fixtures/hcp-setup-build.pkr.hcl +var testHCPBuild string + // Acceptance tests for data sources. // -// To be successful, the HCP project you're providing credentials for must -// contain a bucket named "hardened-ubuntu-16-04", with a channel named -// "packer-acc-test". It must contain a build that references an image in AWS -// region "us-east-1". Your HCP credentials must be provided through your -// runtime environment because the template this test uses does not set them. -// -// TODO: update this acceptance to create and clean up the HCP resources this -// data source queries, to prevent plugin developers from having to have images -// as defined above. - +// Your HCP credentials must be provided through your runtime +// environment because the template this test uses does not set them. func TestAccDatasource_HCPPackerIteration(t *testing.T) { if os.Getenv(env.HCPClientID) == "" && os.Getenv(env.HCPClientSecret) == "" { t.Skipf(fmt.Sprintf("Acceptance tests skipped unless envs %q and %q are set", env.HCPClientID, env.HCPClientSecret)) return } - testCase := &acctest.PluginTestCase{ + tmpFile := filepath.Join(t.TempDir(), "hcp-target-file") + testSetup := acctest.PluginTestCase{ + Template: fmt.Sprintf(testHCPBuild, tmpFile), + Check: func(buildCommand *exec.Cmd, logfile string) error { + if buildCommand.ProcessState != nil { + if buildCommand.ProcessState.ExitCode() != 0 { + return fmt.Errorf("Bad exit code. Logfile: %s", logfile) + } + } + return nil + }, + } + acctest.TestPlugin(t, &testSetup) + + testCase := acctest.PluginTestCase{ Name: "hcp_packer_iteration_datasource_basic_test", - Template: testDatasourceBasic, + Template: fmt.Sprintf(testDatasourceBasic, filepath.Dir(tmpFile)), + Setup: func() error { + if _, err := os.Stat(tmpFile); os.IsNotExist(err) { + return err + } + return nil + }, // TODO have acc test write iteration id to a file and check it to make // sure it isn't empty. Check: func(buildCommand *exec.Cmd, logfile string) error { @@ -49,5 +65,5 @@ func TestAccDatasource_HCPPackerIteration(t *testing.T) { return nil }, } - acctest.TestPlugin(t, testCase) + acctest.TestPlugin(t, &testCase) } diff --git a/datasource/hcp-packer-iteration/test-fixtures/hcp-setup-build.pkr.hcl b/datasource/hcp-packer-iteration/test-fixtures/hcp-setup-build.pkr.hcl new file mode 100644 index 00000000000..f9b203496fb --- /dev/null +++ b/datasource/hcp-packer-iteration/test-fixtures/hcp-setup-build.pkr.hcl @@ -0,0 +1,16 @@ +variable "target" { + type = string + default = %q +} + +source "file" "test" { + content = "Lorem ipsum dolor sit amet" + target = var.target +} + +build { + hcp_packer_registry { + bucket_name = "simple-deprecated" + } + sources = ["source.file.test"] +} diff --git a/datasource/hcp-packer-iteration/test-fixtures/template.pkr.hcl b/datasource/hcp-packer-iteration/test-fixtures/template.pkr.hcl index 52bac83ee7b..931fb023168 100644 --- a/datasource/hcp-packer-iteration/test-fixtures/template.pkr.hcl +++ b/datasource/hcp-packer-iteration/test-fixtures/template.pkr.hcl @@ -3,20 +3,20 @@ source "null" "example" { } data "hcp-packer-iteration" "hardened-source" { - bucket_name = "hardened-ubuntu-16-04" - channel = "packer-acc-test" + bucket_name = "simple-deprecated" + channel = "latest" } -data "hcp-packer-image" "aws" { - bucket_name = "hardened-ubuntu-16-04" +data "hcp-packer-image" "file" { + bucket_name = "simple-deprecated" iteration_id = "${data.hcp-packer-iteration.hardened-source.id}" - cloud_provider = "aws" - region = "us-east-1" + cloud_provider = "packer.file" + region = %q } locals { foo = "${data.hcp-packer-iteration.hardened-source.id}" - bar = "${data.hcp-packer-image.aws.id}" + bar = "${data.hcp-packer-image.file.id}" } build { diff --git a/datasource/hcp-packer-version/data.go b/datasource/hcp-packer-version/data.go new file mode 100644 index 00000000000..a5dcf753f6c --- /dev/null +++ b/datasource/hcp-packer-version/data.go @@ -0,0 +1,149 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:generate packer-sdc struct-markdown +//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config +package hcp_packer_version + +import ( + "context" + "fmt" + "log" + + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/hcl2helper" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/config" + hcpapi "github.com/hashicorp/packer/internal/hcp/api" +) + +type Datasource struct { + config Config +} + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + // The bucket name in the HCP Packer Registry. + BucketName string `mapstructure:"bucket_name" required:"true"` + // The channel name in the given bucket to use for retrieving the version. + ChannelName string `mapstructure:"channel_name" required:"true"` +} + +func (d *Datasource) ConfigSpec() hcldec.ObjectSpec { + return d.config.FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Configure(raws ...interface{}) error { + err := config.Decode(&d.config, nil, raws...) + if err != nil { + return err + } + + var errs *packersdk.MultiError + + if d.config.BucketName == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the `bucket_name` must be specified")) + } + if d.config.ChannelName == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the `channel_name` must be specified")) + } + + if errs != nil && len(errs.Errors) > 0 { + return errs + } + return nil +} + +// DatasourceOutput is essentially a copy of []*models.HashicorpCloudPacker20230101Version, but without +// the build and ancestry details +type DatasourceOutput struct { + // Name of the author who created this version. + AuthorID string `mapstructure:"author_id"` + + // The name of the bucket that this version is associated with. + BucketName string `mapstructure:"bucket_name"` + + // Current state of the version. + Status string `mapstructure:"status"` + + // The date the version was created. + CreatedAt string `mapstructure:"created_at"` + + // The fingerprint of the version; this is a unique identifier set by the Packer build + // that created this version. + Fingerprint string `mapstructure:"fingerprint"` + + // The version ID. This is a ULID, which is a unique identifier similar + // to a UUID. It is created by the HCP Packer Registry when a version is + // first created, and is unique to this version. + ID string `mapstructure:"id"` + + // The version name is created by the HCP Packer Registry once a version is + // "complete". Incomplete or failed versions currently default to having a name "v0". + Name string `mapstructure:"name"` + + // The date when this version was last updated. + UpdatedAt string `mapstructure:"updated_at"` + + // The ID of the channel used to query this version. + ChannelID string `mapstructure:"channel_id"` +} + +func (d *Datasource) OutputSpec() hcldec.ObjectSpec { + return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Execute() (cty.Value, error) { + ctx := context.TODO() + + cli, err := hcpapi.NewClient() + if err != nil { + return cty.NullVal(cty.EmptyObject), err + } + log.Printf( + "[INFO] Reading HCP Packer Version info from HCP Packer Registry (%s) "+ + "[project_id=%s, organization_id=%s, channel=%s]", + d.config.BucketName, cli.ProjectID, cli.OrganizationID, d.config.ChannelName, + ) + + channel, err := cli.GetChannel(ctx, d.config.BucketName, d.config.ChannelName) + if err != nil { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "error retrieving HCP Packer Version from HCP Packer Registry: %s", + err.Error(), + ) + } + if channel.Version == nil { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "there is no HCP Packer Version associated with the channel %s", + d.config.ChannelName, + ) + } + + version := channel.Version + + if *version.Status == hcpPackerModels.HashicorpCloudPacker20230101VersionStatusVERSIONREVOKED { + return cty.NullVal(cty.EmptyObject), fmt.Errorf( + "the HCP Packer Version associated with the channel %s is revoked and can not be used on Packer builds", + d.config.ChannelName, + ) + } + + output := DatasourceOutput{ + AuthorID: version.AuthorID, + BucketName: version.BucketName, + Status: string(*version.Status), + CreatedAt: version.CreatedAt.String(), + Fingerprint: version.Fingerprint, + ID: version.ID, + Name: version.Name, + UpdatedAt: version.UpdatedAt.String(), + ChannelID: channel.ID, + } + + return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil +} diff --git a/datasource/hcp-packer-version/data.hcl2spec.go b/datasource/hcp-packer-version/data.hcl2spec.go new file mode 100644 index 00000000000..20e8e6a1ec7 --- /dev/null +++ b/datasource/hcp-packer-version/data.hcl2spec.go @@ -0,0 +1,88 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package hcp_packer_version + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + BucketName *string `mapstructure:"bucket_name" required:"true" cty:"bucket_name" hcl:"bucket_name"` + ChannelName *string `mapstructure:"channel_name" required:"true" cty:"channel_name" hcl:"channel_name"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "bucket_name": &hcldec.AttrSpec{Name: "bucket_name", Type: cty.String, Required: false}, + "channel_name": &hcldec.AttrSpec{Name: "channel_name", Type: cty.String, Required: false}, + } + return s +} + +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatDatasourceOutput struct { + AuthorID *string `mapstructure:"author_id" cty:"author_id" hcl:"author_id"` + BucketName *string `mapstructure:"bucket_name" cty:"bucket_name" hcl:"bucket_name"` + Status *string `mapstructure:"status" cty:"status" hcl:"status"` + CreatedAt *string `mapstructure:"created_at" cty:"created_at" hcl:"created_at"` + Fingerprint *string `mapstructure:"fingerprint" cty:"fingerprint" hcl:"fingerprint"` + ID *string `mapstructure:"id" cty:"id" hcl:"id"` + Name *string `mapstructure:"name" cty:"name" hcl:"name"` + UpdatedAt *string `mapstructure:"updated_at" cty:"updated_at" hcl:"updated_at"` + ChannelID *string `mapstructure:"channel_id" cty:"channel_id" hcl:"channel_id"` +} + +// FlatMapstructure returns a new FlatDatasourceOutput. +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*DatasourceOutput) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatDatasourceOutput) +} + +// HCL2Spec returns the hcl spec of a DatasourceOutput. +// This spec is used by HCL to read the fields of DatasourceOutput. +// The decoded values from this spec will then be applied to a FlatDatasourceOutput. +func (*FlatDatasourceOutput) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "author_id": &hcldec.AttrSpec{Name: "author_id", Type: cty.String, Required: false}, + "bucket_name": &hcldec.AttrSpec{Name: "bucket_name", Type: cty.String, Required: false}, + "status": &hcldec.AttrSpec{Name: "status", Type: cty.String, Required: false}, + "created_at": &hcldec.AttrSpec{Name: "created_at", Type: cty.String, Required: false}, + "fingerprint": &hcldec.AttrSpec{Name: "fingerprint", Type: cty.String, Required: false}, + "id": &hcldec.AttrSpec{Name: "id", Type: cty.String, Required: false}, + "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, + "updated_at": &hcldec.AttrSpec{Name: "updated_at", Type: cty.String, Required: false}, + "channel_id": &hcldec.AttrSpec{Name: "channel_id", Type: cty.String, Required: false}, + } + return s +} diff --git a/datasource/hcp-packer-version/data_acc_test.go b/datasource/hcp-packer-version/data_acc_test.go new file mode 100644 index 00000000000..e1b54240279 --- /dev/null +++ b/datasource/hcp-packer-version/data_acc_test.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package hcp_packer_version + +import ( + _ "embed" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/acctest" + "github.com/hashicorp/packer/internal/hcp/env" +) + +//go:embed test-fixtures/template.pkr.hcl +var testDatasourceBasic string + +//go:embed test-fixtures/hcp-setup-build.pkr.hcl +var testHCPBuild string + +// Acceptance tests for data sources. +// +// Your HCP credentials must be provided through your runtime +// environment because the template this test uses does not set them. +func TestAccDatasource_HCPPackerVersion(t *testing.T) { + if os.Getenv(env.HCPClientID) == "" && os.Getenv(env.HCPClientSecret) == "" { + t.Skipf(fmt.Sprintf("Acceptance tests skipped unless envs %q and %q are set", env.HCPClientID, env.HCPClientSecret)) + return + } + + tmpFile := filepath.Join(t.TempDir(), "hcp-target-file") + testSetup := acctest.PluginTestCase{ + Template: fmt.Sprintf(testHCPBuild, tmpFile), + Check: func(buildCommand *exec.Cmd, logfile string) error { + if buildCommand.ProcessState != nil { + if buildCommand.ProcessState.ExitCode() != 0 { + return fmt.Errorf("Bad exit code. Logfile: %s", logfile) + } + } + return nil + }, + } + acctest.TestPlugin(t, &testSetup) + + testCase := acctest.PluginTestCase{ + Name: "hcp_packer_version_datasource_basic_test", + Template: fmt.Sprintf(testDatasourceBasic, filepath.Dir(tmpFile)), + Setup: func() error { + if _, err := os.Stat(tmpFile); os.IsNotExist(err) { + return err + } + return nil + }, + // TODO have acc test write version id to a file and check it to make + // sure it isn't empty. + Check: func(buildCommand *exec.Cmd, logfile string) error { + if buildCommand.ProcessState != nil { + if buildCommand.ProcessState.ExitCode() != 0 { + return fmt.Errorf("Bad exit code. Logfile: %s", logfile) + } + } + return nil + }, + } + acctest.TestPlugin(t, &testCase) +} diff --git a/datasource/hcp-packer-version/test-fixtures/hcp-setup-build.pkr.hcl b/datasource/hcp-packer-version/test-fixtures/hcp-setup-build.pkr.hcl new file mode 100644 index 00000000000..6f4f6cbfbf7 --- /dev/null +++ b/datasource/hcp-packer-version/test-fixtures/hcp-setup-build.pkr.hcl @@ -0,0 +1,16 @@ +variable "target" { + type = string + default = %q +} + +source "file" "test" { + content = "Lorem ipsum dolor sit amet" + target = var.target +} + +build { + hcp_packer_registry { + bucket_name = "simple" + } + sources = ["source.file.test"] +} diff --git a/datasource/hcp-packer-version/test-fixtures/template.pkr.hcl b/datasource/hcp-packer-version/test-fixtures/template.pkr.hcl new file mode 100644 index 00000000000..099679b2410 --- /dev/null +++ b/datasource/hcp-packer-version/test-fixtures/template.pkr.hcl @@ -0,0 +1,33 @@ +source "null" "example" { + communicator = "none" +} + +data "hcp-packer-version" "hardened-source" { + bucket_name = "simple" + channel_name = "latest" +} + +data "hcp-packer-artifact" "file" { + bucket_name = "simple" + version_fingerprint = "${data.hcp-packer-version.hardened-source.fingerprint}" + platform = "packer.file" + region = %q +} + +locals { + foo = "${data.hcp-packer-version.hardened-source.id}" + bar = "${data.hcp-packer-artifact.file.external_identifier}" +} + +build { + name = "mybuild" + sources = [ + "source.null.example" + ] + provisioner "shell-local" { + inline = [ + "echo data is ${local.foo}", + "echo data is ${local.bar}" + ] + } +} diff --git a/go.mod b/go.mod index ff209b6cb7f..fb898a3d03a 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcl/v2 v2.19.1 - github.com/hashicorp/hcp-sdk-go v0.81.0 + github.com/hashicorp/hcp-sdk-go v0.82.0 github.com/hashicorp/packer-plugin-amazon v1.2.1 github.com/hashicorp/packer-plugin-sdk v0.5.2 github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 @@ -187,7 +187,6 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b0ad567bd92..bdecb380cef 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= -github.com/hashicorp/hcp-sdk-go v0.81.0 h1:7p/kO7ysGmn0mk7ssAjUvHQROQm3mXoHoxcBnRFdQls= -github.com/hashicorp/hcp-sdk-go v0.81.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk= +github.com/hashicorp/hcp-sdk-go v0.82.0 h1:VdVTVwyqkuDJ3LGvSj4ldpB4aH1HD84seQYxYy0LcSc= +github.com/hashicorp/hcp-sdk-go v0.82.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= @@ -759,9 +759,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/hcp/api/client.go b/internal/hcp/api/client.go index 97a749a33bf..78f8ee37ce1 100644 --- a/internal/hcp/api/client.go +++ b/internal/hcp/api/client.go @@ -11,7 +11,7 @@ import ( "os" "time" - packerSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/client/packer_service" + packerSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" organizationSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/organization_service" projectSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/project_service" rmmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/models" @@ -183,18 +183,18 @@ func getOldestProject(projects []*rmmodels.HashicorpCloudResourcemanagerProject) // ValidateRegistryForProject validates that there is an active registry associated to the configured organization and project ids. // A successful validation will result in a nil response. All other response represent an invalid registry error request or a registry not found error. -func (client *Client) ValidateRegistryForProject() error { +func (c *Client) ValidateRegistryForProject() error { params := packerSvc.NewPackerServiceGetRegistryParams() - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID - resp, err := client.Packer.PackerServiceGetRegistry(params, nil) + resp, err := c.Packer.PackerServiceGetRegistry(params, nil) if err != nil { return err } if resp.GetPayload().Registry == nil { - return fmt.Errorf("No active HCP Packer registry was found for the organization %q and project %q", client.OrganizationID, client.ProjectID) + return fmt.Errorf("No active HCP Packer registry was found for the organization %q and project %q", c.OrganizationID, c.ProjectID) } return nil diff --git a/internal/hcp/api/deprecated_client.go b/internal/hcp/api/deprecated_client.go new file mode 100644 index 00000000000..6f5c06b89d6 --- /dev/null +++ b/internal/hcp/api/deprecated_client.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package api provides access to the HCP Packer Registry API. +package api + +import ( + "fmt" + + packerSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/client/packer_service" + organizationSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/organization_service" + projectSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/project_service" + "github.com/hashicorp/hcp-sdk-go/httpclient" + "github.com/hashicorp/packer/version" +) + +// DeprecatedClient is an HCP client capable of making requests on behalf of a service principal +type DeprecatedClient struct { + Packer packerSvc.ClientService + Organization organizationSvc.ClientService + Project projectSvc.ClientService + OrganizationID string + ProjectID string +} + +// NewDeprecatedClient returns an authenticated client to a HCP Packer Registry. +// Client authentication requires the following environment variables be set HCP_CLIENT_ID and HCP_CLIENT_SECRET. +// Upon error a HCPClientError will be returned. +func NewDeprecatedClient() (*DeprecatedClient, error) { + // Use NewClient to validate HCP configuration provided by user. + tempClient, err := NewClient() + if err != nil { + return nil, err + } + + hcpClientCfg := httpclient.Config{ + SourceChannel: fmt.Sprintf("packer/%s", version.PackerVersion.FormattedVersion()), + } + cl, err := httpclient.New(hcpClientCfg) + if err != nil { + return nil, &ClientError{ + StatusCode: InvalidClientConfig, + Err: err, + } + } + + client := DeprecatedClient{ + Packer: packerSvc.New(cl, nil), + Organization: organizationSvc.New(cl, nil), + Project: projectSvc.New(cl, nil), + OrganizationID: tempClient.OrganizationID, + ProjectID: tempClient.ProjectID, + } + return &client, nil +} diff --git a/internal/hcp/api/deprecated_service.go b/internal/hcp/api/deprecated_service.go new file mode 100644 index 00000000000..9fa88d5dd36 --- /dev/null +++ b/internal/hcp/api/deprecated_service.go @@ -0,0 +1,79 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package api + +import ( + "context" + "fmt" + + hcpPackerDeprecatedAPI "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/client/packer_service" + hcpPackerDeprecatedModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" +) + +type GetIterationOption func(*hcpPackerDeprecatedAPI.PackerServiceGetIterationParams) + +var ( + GetIteration_byID = func(id string) GetIterationOption { + return func(params *hcpPackerDeprecatedAPI.PackerServiceGetIterationParams) { + params.IterationID = &id + } + } + GetIteration_byFingerprint = func(fingerprint string) GetIterationOption { + return func(params *hcpPackerDeprecatedAPI.PackerServiceGetIterationParams) { + params.Fingerprint = &fingerprint + } + } +) + +func (client *DeprecatedClient) GetIteration( + ctx context.Context, bucketSlug string, opts ...GetIterationOption, +) (*hcpPackerDeprecatedModels.HashicorpCloudPackerIteration, error) { + getItParams := hcpPackerDeprecatedAPI.NewPackerServiceGetIterationParams() + getItParams.LocationOrganizationID = client.OrganizationID + getItParams.LocationProjectID = client.ProjectID + getItParams.BucketSlug = bucketSlug + + for _, opt := range opts { + opt(getItParams) + } + + resp, err := client.Packer.PackerServiceGetIteration(getItParams, nil) + if err != nil { + return nil, err + } + + if resp.Payload.Iteration != nil { + return resp.Payload.Iteration, nil + } + + return nil, fmt.Errorf( + "something went wrong retrieving the iteration for bucket %s", bucketSlug, + ) +} + +// GetChannel loads the named channel that is associated to the bucket slug . If the +// channel does not exist in HCP Packer, GetChannel returns an error. +func (client *DeprecatedClient) GetChannel( + ctx context.Context, bucketSlug string, channelName string, +) (*hcpPackerDeprecatedModels.HashicorpCloudPackerChannel, error) { + params := hcpPackerDeprecatedAPI.NewPackerServiceGetChannelParamsWithContext(ctx) + params.LocationOrganizationID = client.OrganizationID + params.LocationProjectID = client.ProjectID + params.BucketSlug = bucketSlug + params.Slug = channelName + + resp, err := client.Packer.PackerServiceGetChannel(params, nil) + if err != nil { + return nil, err + } + + if resp.Payload.Channel == nil { + return nil, fmt.Errorf( + "there is no channel with the name %s associated with the bucket %s", + channelName, bucketSlug, + ) + } + + return resp.Payload.Channel, nil +} diff --git a/internal/hcp/api/mock_service.go b/internal/hcp/api/mock_service.go index ac01c94d002..9a513705052 100644 --- a/internal/hcp/api/mock_service.go +++ b/internal/hcp/api/mock_service.go @@ -9,8 +9,8 @@ import ( "strconv" "github.com/go-openapi/runtime" - packerSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/client/packer_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" + hcpPackerService "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -19,28 +19,28 @@ import ( // Upon calling a service method a boolean is set to true to indicate that a method has been called. // To skip the setting of these booleans set TrackCalledServiceMethods to false; defaults to true in NewMockPackerClientService(). type MockPackerClientService struct { - CreateBucketCalled, UpdateBucketCalled, BucketAlreadyExist bool - CreateIterationCalled, GetIterationCalled, IterationAlreadyExist, IterationCompleted bool - CreateBuildCalled, UpdateBuildCalled, ListBuildsCalled, BuildAlreadyDone bool - TrackCalledServiceMethods bool + CreateBucketCalled, UpdateBucketCalled, BucketAlreadyExist bool + CreateVersionCalled, GetVersionCalled, VersionAlreadyExist, VersionCompleted bool + CreateBuildCalled, UpdateBuildCalled, ListBuildsCalled, BuildAlreadyDone bool + TrackCalledServiceMethods bool // Mock Creates - CreateBucketResp *models.HashicorpCloudPackerCreateBucketResponse - CreateIterationResp *models.HashicorpCloudPackerCreateIterationResponse - CreateBuildResp *models.HashicorpCloudPackerCreateBuildResponse + CreateBucketResp *hcpPackerModels.HashicorpCloudPacker20230101CreateBucketResponse + CreateVersionResp *hcpPackerModels.HashicorpCloudPacker20230101CreateVersionResponse + CreateBuildResp *hcpPackerModels.HashicorpCloudPacker20230101CreateBuildResponse // Mock Gets - GetIterationResp *models.HashicorpCloudPackerGetIterationResponse + GetVersionResp *hcpPackerModels.HashicorpCloudPacker20230101GetVersionResponse ExistingBuilds []string ExistingBuildLabels map[string]string - packerSvc.ClientService + hcpPackerService.ClientService } // NewMockPackerClientService returns a basic mock of the Cloud Packer Service. // Upon calling a service method a boolean is set to true to indicate that a method has been called. -// To skip the setting of these booleans set TrackCalledServiceMethods to false. By default it is true. +// To skip the setting of these booleans set TrackCalledServiceMethods to false. By default, it is true. func NewMockPackerClientService() *MockPackerClientService { m := MockPackerClientService{ ExistingBuilds: make([]string, 0), @@ -51,205 +51,234 @@ func NewMockPackerClientService() *MockPackerClientService { return &m } -func (svc *MockPackerClientService) PackerServiceCreateBucket(params *packerSvc.PackerServiceCreateBucketParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceCreateBucketOK, error) { +func (svc *MockPackerClientService) PackerServiceCreateBucket( + params *hcpPackerService.PackerServiceCreateBucketParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceCreateBucketOK, error) { if svc.BucketAlreadyExist { - return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("Code:%d %s", codes.AlreadyExists, codes.AlreadyExists.String())) + return nil, status.Error( + codes.AlreadyExists, + fmt.Sprintf("Code:%d %s", codes.AlreadyExists, codes.AlreadyExists.String()), + ) } - if params.Body.BucketSlug == "" { - return nil, errors.New("No bucket slug was passed in") + if params.Body.Name == "" { + return nil, errors.New("no bucket name was passed in") } if svc.TrackCalledServiceMethods { svc.CreateBucketCalled = true } - payload := &models.HashicorpCloudPackerCreateBucketResponse{ - Bucket: &models.HashicorpCloudPackerBucket{ + payload := &hcpPackerModels.HashicorpCloudPacker20230101CreateBucketResponse{ + Bucket: &hcpPackerModels.HashicorpCloudPacker20230101Bucket{ ID: "bucket-id", }, } - payload.Bucket.Slug = params.Body.BucketSlug + payload.Bucket.Name = params.Body.Name - ok := &packerSvc.PackerServiceCreateBucketOK{ + ok := &hcpPackerService.PackerServiceCreateBucketOK{ Payload: payload, } return ok, nil } -func (svc *MockPackerClientService) PackerServiceUpdateBucket(params *packerSvc.PackerServiceUpdateBucketParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceUpdateBucketOK, error) { +func (svc *MockPackerClientService) PackerServiceUpdateBucket( + params *hcpPackerService.PackerServiceUpdateBucketParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceUpdateBucketOK, error) { if svc.TrackCalledServiceMethods { svc.UpdateBucketCalled = true } - return packerSvc.NewPackerServiceUpdateBucketOK(), nil + return hcpPackerService.NewPackerServiceUpdateBucketOK(), nil } -func (svc *MockPackerClientService) PackerServiceCreateIteration(params *packerSvc.PackerServiceCreateIterationParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceCreateIterationOK, error) { - if svc.IterationAlreadyExist { - return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("Code:%d %s", codes.AlreadyExists, codes.AlreadyExists.String())) +func (svc *MockPackerClientService) PackerServiceCreateVersion( + params *hcpPackerService.PackerServiceCreateVersionParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceCreateVersionOK, + error) { + if svc.VersionAlreadyExist { + return nil, status.Error( + codes.AlreadyExists, fmt.Sprintf("Code:%d %s", codes.AlreadyExists, + codes.AlreadyExists.String()), + ) } if params.Body.Fingerprint == "" { - return nil, errors.New("No valid Fingerprint was passed in") + return nil, errors.New("no valid Fingerprint was passed in") } if svc.TrackCalledServiceMethods { - svc.CreateIterationCalled = true - } - payload := &models.HashicorpCloudPackerCreateIterationResponse{ - Iteration: &models.HashicorpCloudPackerIteration{ - ID: "iteration-id", + svc.CreateVersionCalled = true + } + payload := &hcpPackerModels.HashicorpCloudPacker20230101CreateVersionResponse{ + Version: &hcpPackerModels.HashicorpCloudPacker20230101Version{ + BucketName: params.BucketName, + Fingerprint: params.Body.Fingerprint, + ID: "version-id", + Name: "v0", + Status: hcpPackerModels.HashicorpCloudPacker20230101VersionStatusVERSIONRUNNING.Pointer(), TemplateType: params.Body.TemplateType, }, } - payload.Iteration.BucketSlug = params.BucketSlug - payload.Iteration.Fingerprint = params.Body.Fingerprint - - ok := &packerSvc.PackerServiceCreateIterationOK{ + ok := &hcpPackerService.PackerServiceCreateVersionOK{ Payload: payload, } return ok, nil } -func (svc *MockPackerClientService) PackerServiceGetIteration(params *packerSvc.PackerServiceGetIterationParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceGetIterationOK, error) { - if !svc.IterationAlreadyExist { +func (svc *MockPackerClientService) PackerServiceGetVersion( + params *hcpPackerService.PackerServiceGetVersionParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceGetVersionOK, error) { + if !svc.VersionAlreadyExist { return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("Code:%d %s", codes.Aborted, codes.Aborted.String())) } - if params.BucketSlug == "" { - return nil, errors.New("No valid BucketSlug was passed in") + if params.BucketName == "" { + return nil, errors.New("no valid BucketName was passed in") } - if params.Fingerprint == nil { - return nil, errors.New("No valid Fingerprint was passed in") + if params.Fingerprint == "" { + return nil, errors.New("no valid Fingerprint was passed in") } if svc.TrackCalledServiceMethods { - svc.GetIterationCalled = true + svc.GetVersionCalled = true } - payload := &models.HashicorpCloudPackerGetIterationResponse{ - Iteration: &models.HashicorpCloudPackerIteration{ - ID: "iteration-id", - Builds: make([]*models.HashicorpCloudPackerBuild, 0), - TemplateType: models.HashicorpCloudPackerIterationTemplateTypeTEMPLATETYPEUNSET.Pointer(), + payload := &hcpPackerModels.HashicorpCloudPacker20230101GetVersionResponse{ + Version: &hcpPackerModels.HashicorpCloudPacker20230101Version{ + ID: "version-id", + Builds: make([]*hcpPackerModels.HashicorpCloudPacker20230101Build, 0), + TemplateType: hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeTEMPLATETYPEUNSET.Pointer(), }, } - payload.Iteration.BucketSlug = params.BucketSlug - payload.Iteration.Fingerprint = *params.Fingerprint - ok := &packerSvc.PackerServiceGetIterationOK{ + payload.Version.BucketName = params.BucketName + payload.Version.Fingerprint = params.Fingerprint + ok := &hcpPackerService.PackerServiceGetVersionOK{ Payload: payload, } - if svc.IterationCompleted { - ok.Payload.Iteration.Complete = true - ok.Payload.Iteration.IncrementalVersion = 1 - ok.Payload.Iteration.Builds = append(ok.Payload.Iteration.Builds, &models.HashicorpCloudPackerBuild{ + if svc.VersionCompleted { + ok.Payload.Version.Name = "v1" + ok.Payload.Version.Builds = append(ok.Payload.Version.Builds, &hcpPackerModels.HashicorpCloudPacker20230101Build{ ID: "build-id", ComponentType: svc.ExistingBuilds[0], - Status: models.HashicorpCloudPackerBuildStatusDONE.Pointer(), - Images: []*models.HashicorpCloudPackerImage{ - {ImageID: "image-id", Region: "somewhere"}, + Status: hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE.Pointer(), + Artifacts: []*hcpPackerModels.HashicorpCloudPacker20230101Artifact{ + {ExternalIdentifier: "image-id", Region: "somewhere"}, }, Labels: make(map[string]string), }) + } else { + ok.Payload.Version.Name = "v0" } return ok, nil } -func (svc *MockPackerClientService) PackerServiceCreateBuild(params *packerSvc.PackerServiceCreateBuildParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceCreateBuildOK, error) { - if params.BucketSlug == "" { - return nil, errors.New("No valid BucketSlug was passed in") +func (svc *MockPackerClientService) PackerServiceCreateBuild( + params *hcpPackerService.PackerServiceCreateBuildParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceCreateBuildOK, error) { + if params.BucketName == "" { + return nil, errors.New("no valid BucketName was passed in") } - if params.Body.Fingerprint == "" { - return nil, errors.New("No valid Fingerprint was passed in") + if params.Fingerprint == "" { + return nil, errors.New("no valid Fingerprint was passed in") } - if params.Body.Build.ComponentType == "" { - return nil, errors.New("No build componentType was passed in") + if params.Body.ComponentType == "" { + return nil, errors.New("no build componentType was passed in") } if svc.TrackCalledServiceMethods { svc.CreateBuildCalled = true } - payload := &models.HashicorpCloudPackerCreateBuildResponse{ - Build: &models.HashicorpCloudPackerBuild{ + payload := &hcpPackerModels.HashicorpCloudPacker20230101CreateBuildResponse{ + Build: &hcpPackerModels.HashicorpCloudPacker20230101Build{ PackerRunUUID: "test-uuid", - Status: models.HashicorpCloudPackerBuildStatusUNSET.Pointer(), + Status: hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET.Pointer(), }, } - payload.Build.ComponentType = params.Body.Build.ComponentType - payload.Build.IterationID = params.IterationID + payload.Build.ComponentType = params.Body.ComponentType - ok := packerSvc.NewPackerServiceCreateBuildOK() + ok := hcpPackerService.NewPackerServiceCreateBuildOK() ok.Payload = payload return ok, nil } -func (svc *MockPackerClientService) PackerServiceUpdateBuild(params *packerSvc.PackerServiceUpdateBuildParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceUpdateBuildOK, error) { +func (svc *MockPackerClientService) PackerServiceUpdateBuild( + params *hcpPackerService.PackerServiceUpdateBuildParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceUpdateBuildOK, error) { if params.BuildID == "" { - return nil, errors.New("No valid BuildID was passed in") + return nil, errors.New("no valid BuildID was passed in") } - if params.Body.Updates == nil { - return nil, errors.New("No valid Updates were passed in") + if params.Body == nil { + return nil, errors.New("no valid Updates were passed in") } - if params.Body.Updates.Status == nil || *params.Body.Updates.Status == "" { - return nil, errors.New("No build status was passed in") + if params.Body.Status == nil || *params.Body.Status == "" { + return nil, errors.New("no build status was passed in") } if svc.TrackCalledServiceMethods { svc.UpdateBuildCalled = true } - ok := packerSvc.NewPackerServiceUpdateBuildOK() - ok.Payload = &models.HashicorpCloudPackerUpdateBuildResponse{ - Build: &models.HashicorpCloudPackerBuild{ + ok := hcpPackerService.NewPackerServiceUpdateBuildOK() + ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101UpdateBuildResponse{ + Build: &hcpPackerModels.HashicorpCloudPacker20230101Build{ ID: params.BuildID, }, } return ok, nil } -func (svc *MockPackerClientService) PackerServiceListBuilds(params *packerSvc.PackerServiceListBuildsParams, _ runtime.ClientAuthInfoWriter, opts ...packerSvc.ClientOption) (*packerSvc.PackerServiceListBuildsOK, error) { +func (svc *MockPackerClientService) PackerServiceListBuilds( + params *hcpPackerService.PackerServiceListBuildsParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceListBuildsOK, error) { - status := models.HashicorpCloudPackerBuildStatusUNSET - images := make([]*models.HashicorpCloudPackerImage, 0) + status := hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET + artifacts := make([]*hcpPackerModels.HashicorpCloudPacker20230101Artifact, 0) labels := make(map[string]string) if svc.BuildAlreadyDone { - status = models.HashicorpCloudPackerBuildStatusDONE - images = append(images, &models.HashicorpCloudPackerImage{ImageID: "image-id", Region: "somewhere"}) + status = hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE + artifacts = append(artifacts, &hcpPackerModels.HashicorpCloudPacker20230101Artifact{ExternalIdentifier: "image-id", Region: "somewhere"}) } for k, v := range svc.ExistingBuildLabels { labels[k] = v } - builds := make([]*models.HashicorpCloudPackerBuild, 0, len(svc.ExistingBuilds)) + builds := make([]*hcpPackerModels.HashicorpCloudPacker20230101Build, 0, len(svc.ExistingBuilds)) for i, name := range svc.ExistingBuilds { - builds = append(builds, &models.HashicorpCloudPackerBuild{ + builds = append(builds, &hcpPackerModels.HashicorpCloudPacker20230101Build{ ID: name + "--" + strconv.Itoa(i), ComponentType: name, - CloudProvider: "mockProvider", - Status: status.Pointer(), - Images: images, + Platform: "mockPlatform", + Status: &status, + Artifacts: artifacts, Labels: labels, }) } - ok := packerSvc.NewPackerServiceListBuildsOK() - ok.Payload = &models.HashicorpCloudPackerListBuildsResponse{ + ok := hcpPackerService.NewPackerServiceListBuildsOK() + ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101ListBuildsResponse{ Builds: builds, } diff --git a/internal/hcp/api/service.go b/internal/hcp/api/service.go deleted file mode 100644 index df56bdbd58c..00000000000 --- a/internal/hcp/api/service.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package api - -import ( - "context" - "errors" - "fmt" - - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/client/packer_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" - "google.golang.org/grpc/codes" -) - -func (client *Client) CreateBucket( - ctx context.Context, - bucketSlug, - bucketDescription string, - bucketLabels map[string]string, -) (*packer_service.PackerServiceCreateBucketOK, error) { - - createBktParams := packer_service.NewPackerServiceCreateBucketParams() - createBktParams.LocationOrganizationID = client.OrganizationID - createBktParams.LocationProjectID = client.ProjectID - createBktParams.Body = packer_service.PackerServiceCreateBucketBody{ - BucketSlug: bucketSlug, - Description: bucketDescription, - Labels: bucketLabels, - } - - return client.Packer.PackerServiceCreateBucket(createBktParams, nil) -} - -func (client *Client) DeleteBucket( - ctx context.Context, - bucketSlug string, -) (*packer_service.PackerServiceDeleteBucketOK, error) { - - deleteBktParams := packer_service.NewPackerServiceDeleteBucketParamsWithContext(ctx) - deleteBktParams.LocationOrganizationID = client.OrganizationID - deleteBktParams.LocationProjectID = client.ProjectID - deleteBktParams.BucketSlug = bucketSlug - - return client.Packer.PackerServiceDeleteBucket(deleteBktParams, nil) -} - -// UpsertBucket tries to create a bucket on a HCP Packer Registry. If the bucket -// exists it will handle the error and update the bucket with the provided -// details. -func (client *Client) UpsertBucket( - ctx context.Context, - bucketSlug, - bucketDescription string, - bucketLabels map[string]string, -) error { - - // Create bucket if exist we continue as is, eventually we want to treat - // this like an upsert - _, err := client.CreateBucket(ctx, bucketSlug, bucketDescription, bucketLabels) - if err != nil && !CheckErrorCode(err, codes.AlreadyExists) { - return err - } - - if err == nil { - return nil - } - - params := packer_service.NewPackerServiceUpdateBucketParamsWithContext(ctx) - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - params.BucketSlug = bucketSlug - params.Body = packer_service.PackerServiceUpdateBucketBody{ - Description: bucketDescription, - Labels: bucketLabels, - } - _, err = client.Packer.PackerServiceUpdateBucket(params, nil) - - return err -} - -func (client *Client) CreateIteration( - ctx context.Context, - bucketSlug, - fingerprint string, - templateType models.HashicorpCloudPackerIterationTemplateType, -) (*packer_service.PackerServiceCreateIterationOK, error) { - - params := packer_service.NewPackerServiceCreateIterationParamsWithContext(ctx) - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - params.BucketSlug = bucketSlug - params.Body = packer_service.PackerServiceCreateIterationBody{ - Fingerprint: fingerprint, - TemplateType: templateType.Pointer(), - } - - return client.Packer.PackerServiceCreateIteration(params, nil) -} - -type GetIterationOption func(*packer_service.PackerServiceGetIterationParams) - -var ( - GetIteration_byID = func(id string) GetIterationOption { - return func(params *packer_service.PackerServiceGetIterationParams) { - params.IterationID = &id - } - } - GetIteration_byFingerprint = func(fingerprint string) GetIterationOption { - return func(params *packer_service.PackerServiceGetIterationParams) { - params.Fingerprint = &fingerprint - } - } -) - -func (client *Client) GetIteration(ctx context.Context, bucketSlug string, opts ...GetIterationOption) (*models.HashicorpCloudPackerIteration, error) { - getItParams := packer_service.NewPackerServiceGetIterationParams() - getItParams.LocationOrganizationID = client.OrganizationID - getItParams.LocationProjectID = client.ProjectID - getItParams.BucketSlug = bucketSlug - - for _, opt := range opts { - opt(getItParams) - } - - resp, err := client.Packer.PackerServiceGetIteration(getItParams, nil) - if err != nil { - return nil, err - } - - if resp.Payload.Iteration != nil { - return resp.Payload.Iteration, nil - } - - return nil, fmt.Errorf("something went wrong retrieving the iteration for bucket %s", bucketSlug) -} - -func (client *Client) CreateBuild( - ctx context.Context, - bucketSlug, - runUUID, - iterationID, - fingerprint, - componentType string, - status models.HashicorpCloudPackerBuildStatus, -) (*packer_service.PackerServiceCreateBuildOK, error) { - - params := packer_service.NewPackerServiceCreateBuildParamsWithContext(ctx) - - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - params.BucketSlug = bucketSlug - params.IterationID = iterationID - params.Body = packer_service.PackerServiceCreateBuildBody{ - Fingerprint: fingerprint, - Build: &models.HashicorpCloudPackerBuildCreateBody{ - ComponentType: componentType, - PackerRunUUID: runUUID, - Status: status.Pointer(), - }, - } - - return client.Packer.PackerServiceCreateBuild(params, nil) -} - -// ListBuilds queries an Iteration on HCP Packer registry for all of it's -// associated builds. Currently all builds are returned regardless of status. -func (client *Client) ListBuilds( - ctx context.Context, - bucketSlug string, - iterationID string, -) ([]*models.HashicorpCloudPackerBuild, error) { - - params := packer_service.NewPackerServiceListBuildsParamsWithContext(ctx) - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - params.BucketSlug = bucketSlug - params.IterationID = iterationID - - resp, err := client.Packer.PackerServiceListBuilds(params, nil) - if err != nil { - return []*models.HashicorpCloudPackerBuild{}, err - } - - return resp.Payload.Builds, nil -} - -// UpdateBuild updates a single iteration build entry with the incoming input -// data. -func (client *Client) UpdateBuild( - ctx context.Context, - buildID, - runUUID, - cloudProvider, - sourceImageID string, - sourceIterationID string, - sourceChannelID string, - labels map[string]string, - status models.HashicorpCloudPackerBuildStatus, - images []*models.HashicorpCloudPackerImageCreateBody, -) (string, error) { - - params := packer_service.NewPackerServiceUpdateBuildParamsWithContext(ctx) - params.BuildID = buildID - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - - params.Body = packer_service.PackerServiceUpdateBuildBody{ - Updates: &models.HashicorpCloudPackerBuildUpdates{ - Images: images, - PackerRunUUID: runUUID, - Labels: labels, - Status: status.Pointer(), - CloudProvider: cloudProvider, - SourceImageID: sourceImageID, - SourceIterationID: sourceIterationID, - SourceChannelID: sourceChannelID, - }, - } - - resp, err := client.Packer.PackerServiceUpdateBuild(params, nil) - if err != nil { - return "", err - } - - if resp == nil { - return "", errors.New("Not sure why response is nil") - } - - return resp.Payload.Build.ID, nil -} - -// GetChannel loads the named channel that is associated to the bucket slug . If the -// channel does not exist in HCP Packer, GetChannel returns an error. -func (client *Client) GetChannel(ctx context.Context, bucketSlug string, channelName string) (*models.HashicorpCloudPackerChannel, error) { - params := packer_service.NewPackerServiceGetChannelParamsWithContext(ctx) - params.LocationOrganizationID = client.OrganizationID - params.LocationProjectID = client.ProjectID - params.BucketSlug = bucketSlug - params.Slug = channelName - - resp, err := client.Packer.PackerServiceGetChannel(params, nil) - if err != nil { - return nil, err - } - - if resp.Payload.Channel == nil { - return nil, fmt.Errorf("there is no channel with the name %s associated with the bucket %s", - channelName, bucketSlug) - } - - return resp.Payload.Channel, nil -} diff --git a/internal/hcp/api/service_bucket.go b/internal/hcp/api/service_bucket.go new file mode 100644 index 00000000000..bfdbe99f809 --- /dev/null +++ b/internal/hcp/api/service_bucket.go @@ -0,0 +1,66 @@ +package api + +import ( + "context" + + hcpPackerService "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + "google.golang.org/grpc/codes" +) + +func (c *Client) CreateBucket( + ctx context.Context, bucketName, bucketDescription string, bucketLabels map[string]string, +) (*hcpPackerService.PackerServiceCreateBucketOK, error) { + + createBktParams := hcpPackerService.NewPackerServiceCreateBucketParams() + createBktParams.LocationOrganizationID = c.OrganizationID + createBktParams.LocationProjectID = c.ProjectID + createBktParams.Body = &hcpPackerModels.HashicorpCloudPacker20230101CreateBucketBody{ + Name: bucketName, + Description: bucketDescription, + Labels: bucketLabels, + } + + return c.Packer.PackerServiceCreateBucket(createBktParams, nil) +} + +func (c *Client) DeleteBucket( + ctx context.Context, bucketName string, +) (*hcpPackerService.PackerServiceDeleteBucketOK, error) { + + deleteBktParams := hcpPackerService.NewPackerServiceDeleteBucketParamsWithContext(ctx) + deleteBktParams.LocationOrganizationID = c.OrganizationID + deleteBktParams.LocationProjectID = c.ProjectID + deleteBktParams.BucketName = bucketName + + return c.Packer.PackerServiceDeleteBucket(deleteBktParams, nil) +} + +// UpsertBucket tries to create a bucket on a HCP Packer Registry. If the bucket exists it will +// handle the error and update the bucket with the provided details. +func (c *Client) UpsertBucket( + ctx context.Context, bucketName, bucketDescription string, bucketLabels map[string]string, +) error { + + // Create bucket if exist we continue as is, eventually we want to treat this like an upsert. + _, err := c.CreateBucket(ctx, bucketName, bucketDescription, bucketLabels) + if err != nil && !CheckErrorCode(err, codes.AlreadyExists) { + return err + } + + if err == nil { + return nil + } + + params := hcpPackerService.NewPackerServiceUpdateBucketParamsWithContext(ctx) + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Body = &hcpPackerModels.HashicorpCloudPacker20230101UpdateBucketBody{ + Description: bucketDescription, + Labels: bucketLabels, + } + _, err = c.Packer.PackerServiceUpdateBucket(params, nil) + + return err +} diff --git a/internal/hcp/api/service_build.go b/internal/hcp/api/service_build.go new file mode 100644 index 00000000000..69ce13b2b34 --- /dev/null +++ b/internal/hcp/api/service_build.go @@ -0,0 +1,93 @@ +package api + +import ( + "context" + "fmt" + + hcpPackerAPI "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" +) + +func (c *Client) CreateBuild( + ctx context.Context, bucketName, runUUID, fingerprint, componentType string, + buildStatus hcpPackerModels.HashicorpCloudPacker20230101BuildStatus, +) (*hcpPackerAPI.PackerServiceCreateBuildOK, error) { + + params := hcpPackerAPI.NewPackerServiceCreateBuildParamsWithContext(ctx) + + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Fingerprint = fingerprint + params.Body = &hcpPackerModels.HashicorpCloudPacker20230101CreateBuildBody{ + ComponentType: componentType, + PackerRunUUID: runUUID, + Status: &buildStatus, + } + + return c.Packer.PackerServiceCreateBuild(params, nil) +} + +// ListBuilds queries a Version on HCP Packer registry for all of it's associated builds. +// Currently, all builds are returned regardless of status. +func (c *Client) ListBuilds( + ctx context.Context, bucketName, fingerprint string, +) ([]*hcpPackerModels.HashicorpCloudPacker20230101Build, error) { + + params := hcpPackerAPI.NewPackerServiceListBuildsParamsWithContext(ctx) + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Fingerprint = fingerprint + + resp, err := c.Packer.PackerServiceListBuilds(params, nil) + if err != nil { + return []*hcpPackerModels.HashicorpCloudPacker20230101Build{}, err + } + + return resp.Payload.Builds, nil +} + +// UpdateBuild updates a single build in a version with the incoming input data. +func (c *Client) UpdateBuild( + ctx context.Context, + bucketName, fingerprint string, + buildID, runUUID, platform, sourceExternalIdentifier string, + parentVersionID string, + parentChannelID string, + buildLabels map[string]string, + buildStatus hcpPackerModels.HashicorpCloudPacker20230101BuildStatus, + artifacts []*hcpPackerModels.HashicorpCloudPacker20230101ArtifactCreateBody, +) (string, error) { + + params := hcpPackerAPI.NewPackerServiceUpdateBuildParamsWithContext(ctx) + params.BuildID = buildID + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Fingerprint = fingerprint + + params.Body = &hcpPackerModels.HashicorpCloudPacker20230101UpdateBuildBody{ + Artifacts: artifacts, + Labels: buildLabels, + PackerRunUUID: runUUID, + ParentChannelID: parentChannelID, + ParentVersionID: parentVersionID, + Platform: platform, + SourceExternalIdentifier: sourceExternalIdentifier, + Status: &buildStatus, + } + + resp, err := c.Packer.PackerServiceUpdateBuild(params, nil) + if err != nil { + return "", err + } + + if resp == nil { + return "", fmt.Errorf( + "something went wrong retrieving the build %s from bucket %s", buildID, bucketName, + ) + } + + return resp.Payload.Build.ID, nil +} diff --git a/internal/hcp/api/service_channel.go b/internal/hcp/api/service_channel.go new file mode 100644 index 00000000000..50dbfe2d84c --- /dev/null +++ b/internal/hcp/api/service_channel.go @@ -0,0 +1,35 @@ +package api + +import ( + "context" + "fmt" + + hcpPackerAPI "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" +) + +// GetChannel loads the named channel that is associated to the bucket name. If the +// channel does not exist in HCP Packer, GetChannel returns an error. +func (c *Client) GetChannel( + ctx context.Context, bucketName, channelName string, +) (*hcpPackerModels.HashicorpCloudPacker20230101Channel, error) { + params := hcpPackerAPI.NewPackerServiceGetChannelParamsWithContext(ctx) + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.ChannelName = channelName + + resp, err := c.Packer.PackerServiceGetChannel(params, nil) + if err != nil { + return nil, err + } + + if resp.Payload.Channel == nil { + return nil, fmt.Errorf( + "there is no channel with the name %s associated with the bucket %s", + channelName, bucketName, + ) + } + + return resp.Payload.Channel, nil +} diff --git a/internal/hcp/api/service_version.go b/internal/hcp/api/service_version.go new file mode 100644 index 00000000000..0e67bc1bd88 --- /dev/null +++ b/internal/hcp/api/service_version.go @@ -0,0 +1,61 @@ +package api + +import ( + "context" + "fmt" + + hcpPackerAPI "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" +) + +const incompleteVersionName = "v0" + +// IsVersionComplete returns if the given version is completed or not. +// +// The best way to know if the version is completed or not is from the name of the version. All version that are +// incomplete are named "v0". +func (c *Client) IsVersionComplete(version *hcpPackerModels.HashicorpCloudPacker20230101Version) bool { + return version.Name != incompleteVersionName +} + +func (c *Client) CreateVersion( + ctx context.Context, + bucketName, + fingerprint string, + templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType, +) (*hcpPackerAPI.PackerServiceCreateVersionOK, error) { + + params := hcpPackerAPI.NewPackerServiceCreateVersionParamsWithContext(ctx) + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Body = &hcpPackerModels.HashicorpCloudPacker20230101CreateVersionBody{ + Fingerprint: fingerprint, + TemplateType: templateType.Pointer(), + } + + return c.Packer.PackerServiceCreateVersion(params, nil) +} + +func (c *Client) GetVersion( + ctx context.Context, bucketName string, fingerprint string, +) (*hcpPackerModels.HashicorpCloudPacker20230101Version, error) { + params := hcpPackerAPI.NewPackerServiceGetVersionParams() + params.LocationOrganizationID = c.OrganizationID + params.LocationProjectID = c.ProjectID + params.BucketName = bucketName + params.Fingerprint = fingerprint + + resp, err := c.Packer.PackerServiceGetVersion(params, nil) + if err != nil { + return nil, err + } + + if resp.Payload.Version != nil { + return resp.Payload.Version, nil + } + + return nil, fmt.Errorf( + "something went wrong retrieving the version for bucket %s", bucketName, + ) +} diff --git a/internal/hcp/registry/artifact.go b/internal/hcp/registry/artifact.go index 7257d302f3d..6ee5467af65 100644 --- a/internal/hcp/registry/artifact.go +++ b/internal/hcp/registry/artifact.go @@ -10,9 +10,9 @@ import ( const BuilderId = "packer.post-processor.hpc-packer-registry" type registryArtifact struct { - BucketSlug string - IterationID string - BuildName string + BucketName string + VersionID string + BuildName string } func (a *registryArtifact) BuilderId() string { @@ -28,7 +28,7 @@ func (a *registryArtifact) Files() []string { } func (a *registryArtifact) String() string { - return fmt.Sprintf("Published metadata to HCP Packer registry packer/%s/iterations/%s", a.BucketSlug, a.IterationID) + return fmt.Sprintf("Published metadata to HCP Packer registry packer/%s/versions/%s", a.BucketName, a.VersionID) } func (*registryArtifact) State(name string) interface{} { diff --git a/internal/hcp/registry/deprecated_ds_config.go b/internal/hcp/registry/deprecated_ds_config.go new file mode 100644 index 00000000000..20c3639807d --- /dev/null +++ b/internal/hcp/registry/deprecated_ds_config.go @@ -0,0 +1,131 @@ +package registry + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" +) + +type hcpImage struct { + ID string + ChannelID string + IterationID string +} + +func imageValueToDSOutput(imageVal map[string]cty.Value) hcpImage { + image := hcpImage{} + for k, v := range imageVal { + switch k { + case "id": + image.ID = v.AsString() + case "channel_id": + image.ChannelID = v.AsString() + case "iteration_id": + image.IterationID = v.AsString() + } + } + + return image +} + +type hcpIteration struct { + ID string + ChannelID string +} + +func iterValueToDSOutput(iterVal map[string]cty.Value) hcpIteration { + iter := hcpIteration{} + for k, v := range iterVal { + switch k { + case "id": + iter.ID = v.AsString() + case "channel_id": + iter.ChannelID = v.AsString() + } + } + return iter +} + +func withDeprecatedDatasourceConfiguration(vals map[string]cty.Value, ui sdkpacker.Ui) bucketConfigurationOpts { + return func(bucket *Bucket) hcl.Diagnostics { + var diags hcl.Diagnostics + + imageDS, imageOK := vals[hcpImageDatasourceType] + iterDS, iterOK := vals[hcpIterationDatasourceType] + + if !imageOK && !iterOK { + return nil + } + + iterations := map[string]hcpIteration{} + + var err error + if iterOK { + ui.Say("[WARN] Deprecation: `hcp-packer-iteration` datasource has been deprecated. " + + "Please use `hcp-packer-version` datasource instead.") + hcpData := map[string]cty.Value{} + err = gocty.FromCtyValue(iterDS, &hcpData) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid HCP datasources", + Detail: fmt.Sprintf("Failed to decode hcp-packer-iteration datasources: %s", err), + }) + return diags + } + + for k, v := range hcpData { + iterVals := v.AsValueMap() + iter := iterValueToDSOutput(iterVals) + iterations[k] = iter + } + } + + images := map[string]hcpImage{} + + if imageOK { + ui.Say("[WARN] Deprecation: `hcp-packer-image` datasource has been deprecated. " + + "Please use `hcp-packer-artifact` datasource instead.") + hcpData := map[string]cty.Value{} + err = gocty.FromCtyValue(imageDS, &hcpData) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid HCP datasources", + Detail: fmt.Sprintf("Failed to decode hcp_packer_image datasources: %s", err), + }) + return diags + } + + for k, v := range hcpData { + imageVals := v.AsValueMap() + img := imageValueToDSOutput(imageVals) + images[k] = img + } + } + + for _, img := range images { + sourceIteration := ParentVersion{} + + sourceIteration.VersionID = img.IterationID + + if img.ChannelID != "" { + sourceIteration.ChannelID = img.ChannelID + } else { + for _, it := range iterations { + if it.ID == img.IterationID { + sourceIteration.ChannelID = it.ChannelID + break + } + } + } + + bucket.SourceExternalIdentifierToParentVersions[img.ID] = sourceIteration + } + + return diags + } +} diff --git a/internal/hcp/registry/ds_config.go b/internal/hcp/registry/ds_config.go new file mode 100644 index 00000000000..e38dd722dca --- /dev/null +++ b/internal/hcp/registry/ds_config.go @@ -0,0 +1,129 @@ +package registry + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" +) + +type hcpVersion struct { + VersionID string + ChannelID string +} + +func versionValueToDSOutput(iterVal map[string]cty.Value) hcpVersion { + version := hcpVersion{} + for k, v := range iterVal { + switch k { + case "id": + version.VersionID = v.AsString() + case "channel_id": + version.ChannelID = v.AsString() + } + } + return version +} + +type hcpArtifact struct { + ExternalIdentifier string + ChannelID string + VersionID string +} + +func artifactValueToDSOutput(imageVal map[string]cty.Value) hcpArtifact { + artifact := hcpArtifact{} + for k, v := range imageVal { + switch k { + case "external_identifier": + artifact.ExternalIdentifier = v.AsString() + case "channel_id": + artifact.ChannelID = v.AsString() + case "version_id": + artifact.VersionID = v.AsString() + } + } + + return artifact +} + +func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationOpts { + return func(bucket *Bucket) hcl.Diagnostics { + var diags hcl.Diagnostics + + versionDS, versionOK := vals[hcpVersionDatasourceType] + artifactDS, artifactOK := vals[hcpArtifactDatasourceType] + + if !artifactOK && !versionOK { + return nil + } + + versions := map[string]hcpVersion{} + + var err error + if versionOK { + hcpData := map[string]cty.Value{} + err = gocty.FromCtyValue(versionDS, &hcpData) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid HCP datasources", + Detail: fmt.Sprintf( + "Failed to decode hcp-packer-version datasources: %s", err, + ), + }) + return diags + } + + for k, v := range hcpData { + versionVals := v.AsValueMap() + version := versionValueToDSOutput(versionVals) + versions[k] = version + } + } + + artifacts := map[string]hcpArtifact{} + + if artifactOK { + hcpData := map[string]cty.Value{} + err = gocty.FromCtyValue(artifactDS, &hcpData) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid HCP datasources", + Detail: fmt.Sprintf( + "Failed to decode hcp-packer-artifact datasources: %s", err, + ), + }) + return diags + } + + for k, v := range hcpData { + artifactVals := v.AsValueMap() + artifact := artifactValueToDSOutput(artifactVals) + artifacts[k] = artifact + } + } + + for _, a := range artifacts { + parentVersion := ParentVersion{} + parentVersion.VersionID = a.VersionID + + if a.ChannelID != "" { + parentVersion.ChannelID = a.ChannelID + } else { + for _, v := range versions { + if v.VersionID == a.VersionID { + parentVersion.ChannelID = v.ChannelID + break + } + } + } + + bucket.SourceExternalIdentifierToParentVersions[a.ExternalIdentifier] = parentVersion + } + + return diags + } +} diff --git a/internal/hcp/registry/errors.go b/internal/hcp/registry/errors.go index 8bc458d241b..3b3dbbd92ba 100644 --- a/internal/hcp/registry/errors.go +++ b/internal/hcp/registry/errors.go @@ -3,7 +3,8 @@ package registry -// ErrBuildAlreadyDone is the error returned by an HCP handler when a build cannot be started since it's already marked as DONE. +// ErrBuildAlreadyDone is the error returned by an HCP handler when a build cannot be started since it's already +// marked as DONE. type ErrBuildAlreadyDone struct { Message string } diff --git a/internal/hcp/registry/hcl.go b/internal/hcp/registry/hcl.go index 531fd648c7e..e0af421ba38 100644 --- a/internal/hcp/registry/hcl.go +++ b/internal/hcp/registry/hcl.go @@ -9,56 +9,61 @@ import ( "log" "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer/hcl2template" "github.com/hashicorp/packer/packer" "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/gocty" ) -// HCLMetadataRegistry is a HCP handler made for handling HCL configurations -type HCLMetadataRegistry struct { +// HCLRegistry is a HCP handler made for handling HCL configurations +type HCLRegistry struct { configuration *hcl2template.PackerConfig bucket *Bucket ui sdkpacker.Ui } const ( - // Known HCP Packer Image Datasource, whose id is the SourceImageId for some build. - hcpImageDatasourceType string = "hcp-packer-image" + // Known HCP Packer Datasource, whose id is the SourceImageId for some build. + hcpImageDatasourceType string = "hcp-packer-image" + hcpArtifactDatasourceType string = "hcp-packer-artifact" + hcpIterationDatasourceType string = "hcp-packer-iteration" - buildLabel string = "build" + hcpVersionDatasourceType string = "hcp-packer-version" + + buildLabel string = "build" ) -// PopulateIteration creates the metadata on HCP for a build -func (h *HCLMetadataRegistry) PopulateIteration(ctx context.Context) error { - err := h.bucket.Initialize(ctx, models.HashicorpCloudPackerIterationTemplateTypeHCL2) +// PopulateVersion creates the metadata in HCP Packer Registry for a build +func (h *HCLRegistry) PopulateVersion(ctx context.Context) error { + err := h.bucket.Initialize(ctx, hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { return err } - err = h.bucket.populateIteration(ctx) + err = h.bucket.populateVersion(ctx) if err != nil { return err } - iterationID := h.bucket.Iteration.ID + versionID := h.bucket.Version.ID - h.configuration.HCPVars["iterationID"] = cty.StringVal(iterationID) + // FIXME: Remove + h.configuration.HCPVars["iterationID"] = cty.StringVal(versionID) + h.configuration.HCPVars["versionID"] = cty.StringVal(versionID) sha, err := getGitSHA(h.configuration.Basedir) if err != nil { log.Printf("failed to get GIT SHA from environment, won't set as build labels") } else { - h.bucket.Iteration.AddSHAToBuildLabels(sha) + h.bucket.Version.AddSHAToBuildLabels(sha) } return nil } // StartBuild is invoked when one build for the configuration is starting to be processed -func (h *HCLMetadataRegistry) StartBuild(ctx context.Context, build sdkpacker.Build) error { +func (h *HCLRegistry) StartBuild(ctx context.Context, build sdkpacker.Build) error { name := build.Name() cb, ok := build.(*packer.CoreBuild) if ok { @@ -68,7 +73,7 @@ func (h *HCLMetadataRegistry) StartBuild(ctx context.Context, build sdkpacker.Bu } // CompleteBuild is invoked when one build for the configuration has finished -func (h *HCLMetadataRegistry) CompleteBuild( +func (h *HCLRegistry) CompleteBuild( ctx context.Context, build sdkpacker.Build, artifacts []sdkpacker.Artifact, @@ -82,20 +87,20 @@ func (h *HCLMetadataRegistry) CompleteBuild( return h.bucket.completeBuild(ctx, name, artifacts, buildErr) } -// IterationStatusSummary prints a status report in the UI if the iteration is not yet done -func (h *HCLMetadataRegistry) IterationStatusSummary() { - h.bucket.Iteration.iterationStatusSummary(h.ui) +// VersionStatusSummary prints a status report in the UI if the version is not yet done +func (h *HCLRegistry) VersionStatusSummary() { + h.bucket.Version.statusSummary(h.ui) } -func NewHCLMetadataRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLMetadataRegistry, hcl.Diagnostics) { +func NewHCLRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLRegistry, hcl.Diagnostics) { var diags hcl.Diagnostics if len(config.Builds) > 1 { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Multiple " + buildLabel + " blocks", - Detail: fmt.Sprintf("For Packer Registry enabled builds, only one " + buildLabel + + Detail: fmt.Sprintf("For HCP Packer Registry enabled builds, only one " + buildLabel + " block can be defined. Please remove any additional " + buildLabel + - " block(s). If this " + buildLabel + " is not meant for the Packer registry please " + + " block(s). If this " + buildLabel + " is not meant for the HCP Packer registry please " + "clear any HCP_PACKER_* environment variables."), }) @@ -105,10 +110,10 @@ func NewHCLMetadataRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) withHCLBucketConfiguration := func(bb *hcl2template.BuildBlock) bucketConfigurationOpts { return func(bucket *Bucket) hcl.Diagnostics { bucket.ReadFromHCLBuildBlock(bb) - // If at this point the bucket.Slug is still empty, + // If at this point the bucket.Name is still empty, // last try is to use the build.Name if present - if bucket.Slug == "" && bb.Name != "" { - bucket.Slug = bb.Name + if bucket.Name == "" && bb.Name != "" { + bucket.Name = bb.Name } // If the description is empty, use the one from the build block @@ -130,6 +135,7 @@ func NewHCLMetadataRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) config.Basedir, withPackerEnvConfiguration, withHCLBucketConfiguration(build), + withDeprecatedDatasourceConfiguration(vals, ui), withDatasourceConfiguration(vals), ) if bucketDiags != nil { @@ -144,128 +150,11 @@ func NewHCLMetadataRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) bucket.RegisterBuildForComponent(source.String()) } - ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Iteration.Fingerprint)) + ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Version.Fingerprint)) - return &HCLMetadataRegistry{ + return &HCLRegistry{ configuration: config, bucket: bucket, ui: ui, }, nil } - -type hcpImage struct { - ID string - ChannelID string - IterationID string -} - -func imageValueToDSOutput(imageVal map[string]cty.Value) hcpImage { - image := hcpImage{} - for k, v := range imageVal { - switch k { - case "id": - image.ID = v.AsString() - case "channel_id": - image.ChannelID = v.AsString() - case "iteration_id": - image.IterationID = v.AsString() - } - } - - return image -} - -type hcpIteration struct { - ID string - ChannelID string -} - -func iterValueToDSOutput(iterVal map[string]cty.Value) hcpIteration { - iter := hcpIteration{} - for k, v := range iterVal { - switch k { - case "id": - iter.ID = v.AsString() - case "channel_id": - iter.ChannelID = v.AsString() - } - } - return iter -} - -func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationOpts { - return func(bucket *Bucket) hcl.Diagnostics { - var diags hcl.Diagnostics - - imageDS, imageOK := vals[hcpImageDatasourceType] - iterDS, iterOK := vals[hcpIterationDatasourceType] - - if !imageOK && !iterOK { - return nil - } - - iterations := map[string]hcpIteration{} - - var err error - if iterOK { - hcpData := map[string]cty.Value{} - err = gocty.FromCtyValue(iterDS, &hcpData) - if err != nil { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid HCP datasources", - Detail: fmt.Sprintf("Failed to decode hcp_packer_iteration datasources: %s", err), - }) - return diags - } - - for k, v := range hcpData { - iterVals := v.AsValueMap() - iter := iterValueToDSOutput(iterVals) - iterations[k] = iter - } - } - - images := map[string]hcpImage{} - - if imageOK { - hcpData := map[string]cty.Value{} - err = gocty.FromCtyValue(imageDS, &hcpData) - if err != nil { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid HCP datasources", - Detail: fmt.Sprintf("Failed to decode hcp_packer_image datasources: %s", err), - }) - return diags - } - - for k, v := range hcpData { - imageVals := v.AsValueMap() - img := imageValueToDSOutput(imageVals) - images[k] = img - } - } - - for _, img := range images { - sourceIteration := ParentIteration{} - - sourceIteration.IterationID = img.IterationID - - if img.ChannelID != "" { - sourceIteration.ChannelID = img.ChannelID - } else { - for _, it := range iterations { - if it.ID == img.IterationID { - sourceIteration.ChannelID = it.ChannelID - break - } - } - } - - bucket.SourceImagesToParentIterations[img.ID] = sourceIteration - } - - return diags - } -} diff --git a/internal/hcp/registry/hcp.go b/internal/hcp/registry/hcp.go index c0d12726e2d..d0d249f25c6 100644 --- a/internal/hcp/registry/hcp.go +++ b/internal/hcp/registry/hcp.go @@ -6,7 +6,7 @@ package registry import ( "fmt" - git "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/packer/hcl2template" "github.com/hashicorp/packer/internal/hcp/env" @@ -71,7 +71,7 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts) }) } - bucket := NewBucketWithIteration() + bucket := NewBucketWithVersion() for _, opt := range opts { if optDiags := opt(bucket); optDiags.HasErrors() { @@ -79,10 +79,10 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts) } } - if bucket.Slug == "" { + if bucket.Name == "" { diags = append(diags, &hcl.Diagnostic{ - Summary: "Image bucket name required", - Detail: "You must provide an image bucket name for HCP Packer builds. " + + Summary: "Bucket name required", + Detail: "You must provide a bucket name for HCP Packer builds. " + "You can set the HCP_PACKER_BUCKET_NAME environment variable. " + "For HCL2 templates, the registry either uses the name of your " + "template's build block, or you can set the bucket_name argument " + @@ -91,20 +91,11 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts) }) } - err := bucket.Iteration.Initialize() + err := bucket.Version.Initialize() if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Failed to initialize iteration", - Detail: fmt.Sprintf("The iteration failed to be initialized for bucket %q: %s", - bucket.Slug, err), - }) - } - - if err != nil { - diags = append(diags, &hcl.Diagnostic{ - Summary: "Iteration initialization failed", - Detail: fmt.Sprintf("Initialization of the iteration failed with "+ + Summary: "Version initialization failed", + Detail: fmt.Sprintf("Initialization of the version failed with "+ "the following error message: %s", err), Severity: hcl.DiagError, }) diff --git a/internal/hcp/registry/json.go b/internal/hcp/registry/json.go index 92aa6db904c..b35f2ea49ed 100644 --- a/internal/hcp/registry/json.go +++ b/internal/hcp/registry/json.go @@ -10,19 +10,19 @@ import ( "path/filepath" "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer/packer" ) -// JSONMetadataRegistry is a HCP handler made to process legacy JSON templates -type JSONMetadataRegistry struct { +// JSONRegistry is a HCP handler made to process legacy JSON templates +type JSONRegistry struct { configuration *packer.Core bucket *Bucket ui sdkpacker.Ui } -func NewJSONMetadataRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONMetadataRegistry, hcl.Diagnostics) { +func NewJSONRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONRegistry, hcl.Diagnostics) { bucket, diags := createConfiguredBucket( filepath.Dir(config.Template.Path), withPackerEnvConfiguration, @@ -46,27 +46,27 @@ func NewJSONMetadataRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONMetadat bucket.RegisterBuildForComponent(buildName) } - ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Iteration.Fingerprint)) + ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Version.Fingerprint)) - return &JSONMetadataRegistry{ + return &JSONRegistry{ configuration: config, bucket: bucket, ui: ui, }, nil } -// PopulateIteration creates the metadata on HCP for a build -func (h *JSONMetadataRegistry) PopulateIteration(ctx context.Context) error { +// PopulateVersion creates the metadata in HCP Packer Registry for a build +func (h *JSONRegistry) PopulateVersion(ctx context.Context) error { err := h.bucket.Validate() if err != nil { return err } - err = h.bucket.Initialize(ctx, models.HashicorpCloudPackerIterationTemplateTypeJSON) + err = h.bucket.Initialize(ctx, hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeJSON) if err != nil { return err } - err = h.bucket.populateIteration(ctx) + err = h.bucket.populateVersion(ctx) if err != nil { return err } @@ -75,19 +75,19 @@ func (h *JSONMetadataRegistry) PopulateIteration(ctx context.Context) error { if err != nil { log.Printf("failed to get GIT SHA from environment, won't set as build labels") } else { - h.bucket.Iteration.AddSHAToBuildLabels(sha) + h.bucket.Version.AddSHAToBuildLabels(sha) } return nil } // StartBuild is invoked when one build for the configuration is starting to be processed -func (h *JSONMetadataRegistry) StartBuild(ctx context.Context, build sdkpacker.Build) error { +func (h *JSONRegistry) StartBuild(ctx context.Context, build sdkpacker.Build) error { return h.bucket.startBuild(ctx, build.Name()) } // CompleteBuild is invoked when one build for the configuration has finished -func (h *JSONMetadataRegistry) CompleteBuild( +func (h *JSONRegistry) CompleteBuild( ctx context.Context, build sdkpacker.Build, artifacts []sdkpacker.Artifact, @@ -96,7 +96,7 @@ func (h *JSONMetadataRegistry) CompleteBuild( return h.bucket.completeBuild(ctx, build.Name(), artifacts, buildErr) } -// IterationStatusSummary prints a status report in the UI if the iteration is not yet done -func (h *JSONMetadataRegistry) IterationStatusSummary() { - h.bucket.Iteration.iterationStatusSummary(h.ui) +// VersionStatusSummary prints a status report in the UI if the version is not yet done +func (h *JSONRegistry) VersionStatusSummary() { + h.bucket.Version.statusSummary(h.ui) } diff --git a/internal/hcp/registry/null_registry.go b/internal/hcp/registry/null_registry.go index f7dbcdf0970..4f32c3d12b3 100644 --- a/internal/hcp/registry/null_registry.go +++ b/internal/hcp/registry/null_registry.go @@ -12,7 +12,7 @@ import ( // nullRegistry is a special handler that does nothing type nullRegistry struct{} -func (r nullRegistry) PopulateIteration(context.Context) error { +func (r nullRegistry) PopulateVersion(context.Context) error { return nil } @@ -29,4 +29,4 @@ func (r nullRegistry) CompleteBuild( return artifacts, nil } -func (r nullRegistry) IterationStatusSummary() {} +func (r nullRegistry) VersionStatusSummary() {} diff --git a/internal/hcp/registry/registry.go b/internal/hcp/registry/registry.go index 35485a8993e..56b70285d41 100644 --- a/internal/hcp/registry/registry.go +++ b/internal/hcp/registry/registry.go @@ -15,13 +15,13 @@ import ( // Registry is an entity capable to orchestrate a Packer build and upload metadata to HCP type Registry interface { - PopulateIteration(context.Context) error + PopulateVersion(context.Context) error StartBuild(context.Context, sdkpacker.Build) error CompleteBuild(ctx context.Context, build sdkpacker.Build, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error) - IterationStatusSummary() + VersionStatusSummary() } -// New instanciates the appropriate registry for the Packer configuration template type. +// New instantiates the appropriate registry for the Packer configuration template type. // A nullRegistry is returned for non-HCP Packer registry enabled templates. func New(cfg packer.Handler, ui sdkpacker.Ui) (Registry, hcl.Diagnostics) { if !IsHCPEnabled(cfg) { @@ -31,9 +31,9 @@ func New(cfg packer.Handler, ui sdkpacker.Ui) (Registry, hcl.Diagnostics) { switch config := cfg.(type) { case *hcl2template.PackerConfig: // Maybe rename to what it represents.... - return NewHCLMetadataRegistry(config, ui) + return NewHCLRegistry(config, ui) case *packer.Core: - return NewJSONMetadataRegistry(config, ui) + return NewJSONRegistry(config, ui) } return nil, hcl.Diagnostics{ diff --git a/internal/hcp/registry/types.bucket.go b/internal/hcp/registry/types.bucket.go index d4239f45005..b8f62a7b2f6 100644 --- a/internal/hcp/registry/types.bucket.go +++ b/internal/hcp/registry/types.bucket.go @@ -13,11 +13,11 @@ import ( "time" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" - "github.com/hashicorp/packer-plugin-sdk/packer" - registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + packerSDK "github.com/hashicorp/packer-plugin-sdk/packer" + packerSDKRegistry "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" "github.com/hashicorp/packer/hcl2template" - "github.com/hashicorp/packer/internal/hcp/api" + hcpPackerAPI "github.com/hashicorp/packer/internal/hcp/api" "github.com/hashicorp/packer/internal/hcp/env" "github.com/mitchellh/mapstructure" "google.golang.org/grpc/codes" @@ -27,48 +27,52 @@ import ( // build is still alive. const HeartbeatPeriod = 2 * time.Minute -// Bucket represents a single Image bucket on the HCP Packer registry. +// Bucket represents a single bucket on the HCP Packer registry. type Bucket struct { - Slug string - Description string - Destination string - BucketLabels map[string]string - BuildLabels map[string]string - SourceImagesToParentIterations map[string]ParentIteration - RunningBuilds map[string]chan struct{} - Iteration *Iteration - client *api.Client + Name string + Description string + Destination string + BucketLabels map[string]string + BuildLabels map[string]string + SourceExternalIdentifierToParentVersions map[string]ParentVersion + RunningBuilds map[string]chan struct{} + Version *Version + client *hcpPackerAPI.Client } -type ParentIteration struct { - IterationID string - ChannelID string +type ParentVersion struct { + VersionID string + ChannelID string } -// NewBucketWithIteration initializes a simple Bucket that can be used publishing Packer build -// images to the HCP Packer registry. -func NewBucketWithIteration() *Bucket { +// NewBucketWithVersion initializes a simple Bucket that can be used for publishing Packer build artifacts +// to the HCP Packer registry. +func NewBucketWithVersion() *Bucket { b := Bucket{ - BucketLabels: make(map[string]string), - BuildLabels: make(map[string]string), - SourceImagesToParentIterations: make(map[string]ParentIteration), - RunningBuilds: make(map[string]chan struct{}), + BucketLabels: make(map[string]string), + BuildLabels: make(map[string]string), + SourceExternalIdentifierToParentVersions: make(map[string]ParentVersion), + RunningBuilds: make(map[string]chan struct{}), } - b.Iteration = NewIteration() + b.Version = NewVersion() return &b } -func (b *Bucket) Validate() error { - if b.Slug == "" { - return fmt.Errorf("no Packer bucket name defined; either the environment variable %q is undefined or the HCL configuration has no build name", env.HCPPackerBucket) +func (bucket *Bucket) Validate() error { + if bucket.Name == "" { + return fmt.Errorf( + "no Packer bucket name defined; either the environment variable %q is undefined or "+ + "the HCL configuration has no build name", + env.HCPPackerBucket, + ) } return nil } // ReadFromHCLBuildBlock reads the information for initialising a Bucket from a HCL2 build block -func (b *Bucket) ReadFromHCLBuildBlock(build *hcl2template.BuildBlock) { - if b == nil { +func (bucket *Bucket) ReadFromHCLBuildBlock(build *hcl2template.BuildBlock) { + if bucket == nil { return } @@ -77,76 +81,77 @@ func (b *Bucket) ReadFromHCLBuildBlock(build *hcl2template.BuildBlock) { return } - b.Description = registryBlock.Description - b.BucketLabels = registryBlock.BucketLabels - b.BuildLabels = registryBlock.BuildLabels - // If there's already a Slug this was set from env variable. + bucket.Description = registryBlock.Description + bucket.BucketLabels = registryBlock.BucketLabels + bucket.BuildLabels = registryBlock.BuildLabels + // If there's already a Name this was set from env variable. // In Packer, env variable overrides config values so we keep it that way for consistency. - if b.Slug == "" && registryBlock.Slug != "" { - b.Slug = registryBlock.Slug + if bucket.Name == "" && registryBlock.Slug != "" { + bucket.Name = registryBlock.Slug } } // connect initializes a client connection to a remote HCP Packer Registry service on HCP. // Upon a successful connection the initialized client is persisted on the Bucket b for later usage. -func (b *Bucket) connect() error { - if b.client != nil { +func (bucket *Bucket) connect() error { + if bucket.client != nil { return nil } - registryClient, err := api.NewClient() + registryClient, err := hcpPackerAPI.NewClient() if err != nil { return errors.New("Failed to create client connection to artifact registry: " + err.Error()) } - b.client = registryClient + bucket.client = registryClient return nil } -// Initialize registers the Bucket b with the configured HCP Packer Registry. -// Upon initialization a Bucket will be upserted to, and new iteration will be created for the build if the configured -// fingerprint has no associated iterations. Lastly, the initialization process with register the builds that need to be -// completed before an iteration can be marked as DONE. +// Initialize registers the bucket with the configured HCP Packer Registry. +// Upon initialization a Bucket will be upserted to, and new version will be created for the build if the configured +// fingerprint has no associated versions. Lastly, the initialization process with register the builds that need to be +// completed before an version can be marked as DONE. // // b.Initialize() must be called before any data can be published to the configured HCP Packer Registry. // TODO ensure initialize can only be called once -func (b *Bucket) Initialize(ctx context.Context, templateType models.HashicorpCloudPackerIterationTemplateType) error { +func (bucket *Bucket) Initialize( + ctx context.Context, templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType, +) error { - if err := b.connect(); err != nil { + if err := bucket.connect(); err != nil { return err } - b.Destination = fmt.Sprintf("%s/%s", b.client.OrganizationID, b.client.ProjectID) + bucket.Destination = fmt.Sprintf("%s/%s", bucket.client.OrganizationID, bucket.client.ProjectID) - err := b.client.UpsertBucket(ctx, b.Slug, b.Description, b.BucketLabels) + err := bucket.client.UpsertBucket(ctx, bucket.Name, bucket.Description, bucket.BucketLabels) if err != nil { - return fmt.Errorf("failed to initialize bucket %q: %w", b.Slug, err) + return fmt.Errorf("failed to initialize bucket %q: %w", bucket.Name, err) } - return b.initializeIteration(ctx, templateType) + return bucket.initializeVersion(ctx, templateType) } -func (b *Bucket) RegisterBuildForComponent(sourceName string) { - if b == nil { +func (bucket *Bucket) RegisterBuildForComponent(sourceName string) { + if bucket == nil { return } - if ok := b.Iteration.HasBuild(sourceName); ok { + if ok := bucket.Version.HasBuild(sourceName); ok { return } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, sourceName) + bucket.Version.expectedBuilds = append(bucket.Version.expectedBuilds, sourceName) } -// CreateInitialBuildForIteration will create a build entry on the HCP Packer Registry for the named componentType. -// This initial creation is needed so that Packer can properly track when an iteration is complete. -func (b *Bucket) CreateInitialBuildForIteration(ctx context.Context, componentType string) error { - status := models.HashicorpCloudPackerBuildStatusUNSET +// CreateInitialBuildForVersion will create a build entry on the HCP Packer Registry for the named componentType. +// This initial creation is needed so that Packer can properly track when an version is complete. +func (bucket *Bucket) CreateInitialBuildForVersion(ctx context.Context, componentType string) error { + status := hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET - resp, err := b.client.CreateBuild(ctx, - b.Slug, - b.Iteration.RunUUID, - b.Iteration.ID, - b.Iteration.Fingerprint, + resp, err := bucket.client.CreateBuild(ctx, + bucket.Name, + bucket.Version.RunUUID, + bucket.Version.Fingerprint, componentType, status, ) @@ -160,27 +165,30 @@ func (b *Bucket) CreateInitialBuildForIteration(ctx context.Context, componentTy } build.Labels = make(map[string]string) - build.Images = make(map[string]registryimage.Image) + build.Artifacts = make(map[string]packerSDKRegistry.Image) // Initial build labels are only pushed to the registry when an actual Packer run is executed on the said build. - // For example filtered builds (e.g --only or except) will not get the initial build labels until a build is executed on them. - // Global build label updates to existing builds are handled in PopulateIteration. - if len(b.BuildLabels) > 0 { - build.MergeLabels(b.BuildLabels) + // For example filtered builds (e.g --only or except) will not get the initial build labels until a build is + // executed on them. + // Global build label updates to existing builds are handled in PopulateVersion. + if len(bucket.BuildLabels) > 0 { + build.MergeLabels(bucket.BuildLabels) } - b.Iteration.StoreBuild(componentType, build) + bucket.Version.StoreBuild(componentType, build) return nil } // UpdateBuildStatus updates the status of a build entry on the HCP Packer registry with its current local status. // For updating a build status to DONE use CompleteBuild. -func (b *Bucket) UpdateBuildStatus(ctx context.Context, name string, status models.HashicorpCloudPackerBuildStatus) error { - if status == models.HashicorpCloudPackerBuildStatusDONE { +func (bucket *Bucket) UpdateBuildStatus( + ctx context.Context, name string, status hcpPackerModels.HashicorpCloudPacker20230101BuildStatus, +) error { + if status == hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE { return fmt.Errorf("do not use UpdateBuildStatus for updating to DONE") } - buildToUpdate, err := b.Iteration.Build(name) + buildToUpdate, err := bucket.Version.Build(name) if err != nil { return err } @@ -189,11 +197,13 @@ func (b *Bucket) UpdateBuildStatus(ctx context.Context, name string, status mode return fmt.Errorf("the build for the component %q does not have a valid id", name) } - if buildToUpdate.Status == models.HashicorpCloudPackerBuildStatusDONE { + if buildToUpdate.Status == hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE { return fmt.Errorf("cannot modify status of DONE build %s", name) } - _, err = b.client.UpdateBuild(ctx, + _, err = bucket.client.UpdateBuild(ctx, + bucket.Name, + bucket.Version.Fingerprint, buildToUpdate.ID, buildToUpdate.RunUUID, "", @@ -208,15 +218,15 @@ func (b *Bucket) UpdateBuildStatus(ctx context.Context, name string, status mode return err } buildToUpdate.Status = status - b.Iteration.StoreBuild(name, buildToUpdate) + bucket.Version.StoreBuild(name, buildToUpdate) return nil } // markBuildComplete should be called to set a build on the HCP Packer registry to DONE. -// Upon a successful call markBuildComplete will publish all images created by the named build, -// and set the registry build to done. A build with no images can not be set to DONE. -func (b *Bucket) markBuildComplete(ctx context.Context, name string) error { - buildToUpdate, err := b.Iteration.Build(name) +// Upon a successful call markBuildComplete will publish all artifacts created by the named build, +// and set the build to done. A build with no artifacts can not be set to DONE. +func (bucket *Bucket) markBuildComplete(ctx context.Context, name string) error { + buildToUpdate, err := bucket.Version.Build(name) if err != nil { return err } @@ -225,157 +235,184 @@ func (b *Bucket) markBuildComplete(ctx context.Context, name string) error { return fmt.Errorf("the build for the component %q does not have a valid id", name) } - status := models.HashicorpCloudPackerBuildStatusDONE + status := hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE if buildToUpdate.Status == status { // let's no mess with anything that is already done return nil } - if len(buildToUpdate.Images) == 0 { - return fmt.Errorf("setting a build to DONE with no published images is not currently supported.") + if len(buildToUpdate.Artifacts) == 0 { + return fmt.Errorf("setting a build to DONE with no published artifacts is not currently supported") } - var providerName, sourceID, sourceIterationID, sourceChannelID string - images := make([]*models.HashicorpCloudPackerImageCreateBody, 0, len(buildToUpdate.Images)) - for _, image := range buildToUpdate.Images { - // These values will always be the same for all images in a single build, + var platformName, sourceID, parentVersionID, parentChannelID string + artifacts := make([]*hcpPackerModels.HashicorpCloudPacker20230101ArtifactCreateBody, 0, len(buildToUpdate.Artifacts)) + for _, artifact := range buildToUpdate.Artifacts { + // These values will always be the same for all artifacts in a single build, // so we can just set it inside the loop without consequence - if providerName == "" { - providerName = image.ProviderName + if platformName == "" { + platformName = artifact.ProviderName } - if image.SourceImageID != "" { - sourceID = image.SourceImageID + if artifact.SourceImageID != "" { + sourceID = artifact.SourceImageID } - // Check if image is using some other HCP Packer image - if v, ok := b.SourceImagesToParentIterations[image.SourceImageID]; ok { - sourceIterationID = v.IterationID - sourceChannelID = v.ChannelID + // Check if artifact is using some other HCP Packer artifact + if v, ok := bucket.SourceExternalIdentifierToParentVersions[artifact.SourceImageID]; ok { + parentVersionID = v.VersionID + parentChannelID = v.ChannelID } - images = append(images, &models.HashicorpCloudPackerImageCreateBody{ImageID: image.ImageID, Region: image.ProviderRegion}) + artifacts = append( + artifacts, + &hcpPackerModels.HashicorpCloudPacker20230101ArtifactCreateBody{ + ExternalIdentifier: artifact.ImageID, + Region: artifact.ProviderRegion, + }, + ) } - _, err = b.client.UpdateBuild(ctx, + _, err = bucket.client.UpdateBuild(ctx, + bucket.Name, + bucket.Version.Fingerprint, buildToUpdate.ID, buildToUpdate.RunUUID, - buildToUpdate.CloudProvider, + buildToUpdate.Platform, sourceID, - sourceIterationID, - sourceChannelID, + parentVersionID, + parentChannelID, buildToUpdate.Labels, status, - images, + artifacts, ) if err != nil { return err } buildToUpdate.Status = status - b.Iteration.StoreBuild(name, buildToUpdate) + bucket.Version.StoreBuild(name, buildToUpdate) return nil } -// UpdateImageForBuild appends one or more images artifacts to the build referred to by componentType. -func (b *Bucket) UpdateImageForBuild(componentType string, images ...registryimage.Image) error { - return b.Iteration.AddImageToBuild(componentType, images...) +// UpdateArtifactForBuild appends one or more artifacts to the build referred to by componentType. +func (bucket *Bucket) UpdateArtifactForBuild(componentType string, artifacts ...packerSDKRegistry.Image) error { + return bucket.Version.AddArtifactToBuild(componentType, artifacts...) } // UpdateLabelsForBuild merges the contents of data to the labels associated with the build referred to by componentType. -func (b *Bucket) UpdateLabelsForBuild(componentType string, data map[string]string) error { - return b.Iteration.AddLabelsToBuild(componentType, data) +func (bucket *Bucket) UpdateLabelsForBuild(componentType string, data map[string]string) error { + return bucket.Version.AddLabelsToBuild(componentType, data) } -// Load defaults from environment variables -func (b *Bucket) LoadDefaultSettingsFromEnv() { +// LoadDefaultSettingsFromEnv loads defaults from environment variables +func (bucket *Bucket) LoadDefaultSettingsFromEnv() { // Configure HCP Packer Registry destination - if b.Slug == "" { - b.Slug = os.Getenv(env.HCPPackerBucket) + if bucket.Name == "" { + bucket.Name = os.Getenv(env.HCPPackerBucket) } - // Set some iteration values. For Packer RunUUID should always be set. - // Creating an iteration differently? Let's not overwrite a UUID that might be set. - if b.Iteration.RunUUID == "" { - b.Iteration.RunUUID = os.Getenv("PACKER_RUN_UUID") + // Set some version values. For Packer RunUUID should always be set. + // Creating an version differently? Let's not overwrite a UUID that might be set. + if bucket.Version.RunUUID == "" { + bucket.Version.RunUUID = os.Getenv("PACKER_RUN_UUID") } } -// createIteration creates an empty iteration for a give bucket on the HCP Packer registry. -// The iteration can then be stored locally and used for tracking build status and images for a running +// createVersion creates an empty version for a given bucket on the HCP Packer registry. +// The version can then be stored locally and used for tracking build status and artifacts for a running // Packer build. -func (b *Bucket) createIteration(templateType models.HashicorpCloudPackerIterationTemplateType) (*models.HashicorpCloudPackerIteration, error) { +func (bucket *Bucket) createVersion( + templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType, +) (*hcpPackerModels.HashicorpCloudPacker20230101Version, error) { ctx := context.Background() - if templateType == models.HashicorpCloudPackerIterationTemplateTypeTEMPLATETYPEUNSET { - return nil, fmt.Errorf("packer error: template type should not be unset when creating an iteration. This is a Packer internal bug which should be reported to the development team for a fix.") + if templateType == hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeTEMPLATETYPEUNSET { + return nil, fmt.Errorf( + "packer error: template type should not be unset when creating a version. " + + "This is a Packer internal bug which should be reported to the development team for a fix", + ) } - createIterationResp, err := b.client.CreateIteration(ctx, b.Slug, b.Iteration.Fingerprint, templateType) + createVersionResp, err := bucket.client.CreateVersion( + ctx, bucket.Name, bucket.Version.Fingerprint, templateType, + ) if err != nil { - return nil, fmt.Errorf("failed to create Iteration for Bucket %s with error: %w", b.Slug, err) + return nil, fmt.Errorf("failed to create Version for Bucket %s with error: %w", bucket.Name, err) } - if createIterationResp == nil { - return nil, fmt.Errorf("failed to create Iteration for Bucket %s with error: %w", b.Slug, err) + if createVersionResp == nil { + return nil, fmt.Errorf("failed to create Version for Bucket %s with error: %w", bucket.Name, err) } - log.Println("[TRACE] a valid iteration for build was created with the Id", createIterationResp.Payload.Iteration.ID) - return createIterationResp.Payload.Iteration, nil + log.Println( + "[TRACE] a valid version for build was created with the Id", createVersionResp.Payload.Version.ID, + ) + return createVersionResp.Payload.Version, nil } -func (b *Bucket) initializeIteration(ctx context.Context, templateType models.HashicorpCloudPackerIterationTemplateType) error { - // load existing iteration using fingerprint. - iteration, err := b.client.GetIteration(ctx, b.Slug, api.GetIteration_byFingerprint(b.Iteration.Fingerprint)) - if api.CheckErrorCode(err, codes.Aborted) { - // probably means Iteration doesn't exist need a way to check the error - iteration, err = b.createIteration(templateType) +func (bucket *Bucket) initializeVersion( + ctx context.Context, templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType, +) error { + // load existing version using fingerprint. + version, err := bucket.client.GetVersion(ctx, bucket.Name, bucket.Version.Fingerprint) + if hcpPackerAPI.CheckErrorCode(err, codes.Aborted) { + // probably means Version doesn't exist need a way to check the error + version, err = bucket.createVersion(templateType) } if err != nil { - return fmt.Errorf("failed to initialize iteration for fingerprint %s: %s", b.Iteration.Fingerprint, err) + return fmt.Errorf("failed to initialize version for fingerprint %s: %s", bucket.Version.Fingerprint, err) } - if iteration == nil { - return fmt.Errorf("failed to initialize iteration details for Bucket %s with error: %w", b.Slug, err) + if version == nil { + return fmt.Errorf("failed to initialize version details for Bucket %s with error: %w", bucket.Name, err) } - if iteration.TemplateType != nil && - *iteration.TemplateType != models.HashicorpCloudPackerIterationTemplateTypeTEMPLATETYPEUNSET && - *iteration.TemplateType != templateType { - return fmt.Errorf("This iteration was initially created with a %[2]s template. Changing from %[2]s to %[1]s is not supported.", - templateType, *iteration.TemplateType) + if version.TemplateType != nil && + *version.TemplateType != hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeTEMPLATETYPEUNSET && + *version.TemplateType != templateType { + return fmt.Errorf( + "This version was initially created with a %[2]s template. "+ + "Changing from %[2]s to %[1]s is not supported", + templateType, *version.TemplateType, + ) } - log.Println("[TRACE] a valid iteration was retrieved with the id", iteration.ID) - b.Iteration.ID = iteration.ID + log.Println( + "[TRACE] a valid version was retrieved with the id", version.ID, + ) + bucket.Version.ID = version.ID - // If the iteration is completed and there are no new builds to add, Packer + // If the version is completed and there are no new builds to add, Packer // should exit and inform the user that artifacts already exists for the - // fingerprint associated with the iteration. - if iteration.Complete { - return fmt.Errorf("This iteration associated to the fingerprint %s is complete. "+ - "If you wish to add a new build to this image a new iteration must be created by changing the build fingerprint.", b.Iteration.Fingerprint) + // fingerprint associated with the version. + if bucket.client.IsVersionComplete(version) { + return fmt.Errorf( + "The version associated to the fingerprint %v is complete. If you wish to add a new build to "+ + "this bucket a new version must be created by changing the fingerprint.", + bucket.Version.Fingerprint, + ) } return nil } -// populateIteration populates the bucket iteration with the details needed for tracking builds for a Packer run. -// If an existing Packer registry iteration exists for the said iteration fingerprint, calling initialize on iteration -// that doesn't yet exist will call createIteration to create the entry on the HCP packer registry for the given bucket. -// All build details will be created (if they don't exists) and added to b.Iteration.builds for tracking during runtime. -func (b *Bucket) populateIteration(ctx context.Context) error { - // list all this iteration's builds so we can figure out which ones +// populateVersion populates the version with the details needed for tracking builds for a Packer run. +// If a version exists for the said fingerprint, calling initialize on version that doesn't yet exist will call +// createVersion to create the entry on the HCP packer registry for the given bucket. +// All build details will be created (if they don't exist) and added to b.Version.builds for tracking during runtime. +func (bucket *Bucket) populateVersion(ctx context.Context) error { + // list all this version's builds so we can figure out which ones // we want to run against. TODO: pagination? - existingBuilds, err := b.client.ListBuilds(ctx, b.Slug, b.Iteration.ID) + existingBuilds, err := bucket.client.ListBuilds(ctx, bucket.Name, bucket.Version.Fingerprint) if err != nil { - return fmt.Errorf("error listing builds for this existing iteration: %s", err) + return fmt.Errorf("error listing builds for this existing version: %s", err) } var toCreate []string - for _, expected := range b.Iteration.expectedBuilds { + for _, expected := range bucket.Version.expectedBuilds { var found bool for _, existing := range existingBuilds { @@ -387,25 +424,27 @@ func (b *Bucket) populateIteration(ctx context.Context) error { } // When running against an existing build the Packer RunUUID is most likely different. - // We capture that difference here to know that the image was created in a different Packer run. - build.RunUUID = b.Iteration.RunUUID + // We capture that difference here to know that the artifacts were created in a different Packer run. + build.RunUUID = bucket.Version.RunUUID // When bucket build labels represent some dynamic data set, possibly set via some user variable, // we need to make sure that any newly executed builds get the labels at runtime. - if build.IsNotDone() && len(b.BuildLabels) > 0 { - build.MergeLabels(b.BuildLabels) + if build.IsNotDone() && len(bucket.BuildLabels) > 0 { + build.MergeLabels(bucket.BuildLabels) } - log.Printf("[TRACE] a build of component type %s already exists; skipping the create call", expected) - b.Iteration.StoreBuild(existing.ComponentType, build) + log.Printf( + "[TRACE] a build of component type %s already exists; skipping the create call", expected, + ) + bucket.Version.StoreBuild(existing.ComponentType, build) break } } if !found { - missingbuild := expected - toCreate = append(toCreate, missingbuild) + missingBuild := expected + toCreate = append(toCreate, missingBuild) } } @@ -421,10 +460,10 @@ func (b *Bucket) populateIteration(ctx context.Context) error { go func(name string) { defer wg.Done() - log.Printf("[TRACE] registering build with iteration for %q.", name) - err := b.CreateInitialBuildForIteration(ctx, name) + log.Printf("[TRACE] registering build with version for %q.", name) + err := bucket.CreateInitialBuildForVersion(ctx, name) - if api.CheckErrorCode(err, codes.AlreadyExists) { + if hcpPackerAPI.CheckErrorCode(err, codes.AlreadyExists) { log.Printf("[TRACE] build %s already exists in Packer registry, continuing...", name) return } @@ -441,14 +480,14 @@ func (b *Bucket) populateIteration(ctx context.Context) error { return errs.ErrorOrNil() } -// IsExpectingBuildForComponent returns true if the component referenced by buildName is part of the iteration +// IsExpectingBuildForComponent returns true if the component referenced by buildName is part of the version // and is not marked as DONE on the HCP Packer registry. -func (b *Bucket) IsExpectingBuildForComponent(buildName string) bool { - if !b.Iteration.HasBuild(buildName) { +func (bucket *Bucket) IsExpectingBuildForComponent(buildName string) bool { + if !bucket.Version.HasBuild(buildName) { return false } - build, err := b.Iteration.Build(buildName) + build, err := bucket.Version.Build(buildName) if err != nil { return false } @@ -462,8 +501,8 @@ func (b *Bucket) IsExpectingBuildForComponent(buildName string) bool { // as cancelled by the HCP Packer registry service. // // Usage: defer (b.HeartbeatBuild(ctx, build, period))() -func (b *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), error) { - buildToUpdate, err := b.Iteration.Build(build) +func (bucket *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), error) { + buildToUpdate, err := bucket.Version.Build(build) if err != nil { return nil, err } @@ -484,7 +523,9 @@ func (b *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), erro tick.Stop() break outHeartbeats case <-tick.C: - _, err = b.client.UpdateBuild(ctx, + _, err = bucket.client.UpdateBuild(ctx, + bucket.Name, + bucket.Version.Fingerprint, buildToUpdate.ID, buildToUpdate.RunUUID, "", @@ -492,7 +533,7 @@ func (b *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), erro "", "", nil, - models.HashicorpCloudPackerBuildStatusRUNNING, + hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING, nil, ) if err != nil { @@ -510,19 +551,19 @@ func (b *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), erro }, nil } -func (b *Bucket) startBuild(ctx context.Context, buildName string) error { - if !b.IsExpectingBuildForComponent(buildName) { +func (bucket *Bucket) startBuild(ctx context.Context, buildName string) error { + if !bucket.IsExpectingBuildForComponent(buildName) { return &ErrBuildAlreadyDone{ Message: "build is already done", } } - err := b.UpdateBuildStatus(ctx, buildName, models.HashicorpCloudPackerBuildStatusRUNNING) + err := bucket.UpdateBuildStatus(ctx, buildName, hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING) if err != nil { - return fmt.Errorf("failed to update HCP Packer registry status for %q: %s", buildName, err) + return fmt.Errorf("failed to update HCP Packer Build status for %q: %s", buildName, err) } - cleanupHeartbeat, err := b.HeartbeatBuild(ctx, buildName) + cleanupHeartbeat, err := bucket.HeartbeatBuild(ctx, buildName) if err != nil { log.Printf("[ERROR] failed to start heartbeat function") } @@ -533,13 +574,13 @@ func (b *Bucket) startBuild(ctx context.Context, buildName string) error { select { case <-ctx.Done(): cleanupHeartbeat() - err := b.UpdateBuildStatus( + err := bucket.UpdateBuildStatus( context.Background(), buildName, - models.HashicorpCloudPackerBuildStatusCANCELLED) + hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDCANCELLED) if err != nil { log.Printf( - "[ERROR] failed to update HCP Packer registry status for %q: %s", + "[ERROR] failed to update HCP Packer Build status for %q: %s", buildName, err) } @@ -549,18 +590,18 @@ func (b *Bucket) startBuild(ctx context.Context, buildName string) error { log.Printf("[TRACE] done waiting for heartbeat completion") }() - b.RunningBuilds[buildName] = buildDone + bucket.RunningBuilds[buildName] = buildDone return nil } -func (b *Bucket) completeBuild( +func (bucket *Bucket) completeBuild( ctx context.Context, buildName string, - artifacts []packer.Artifact, + packerSDKArtifacts []packerSDK.Artifact, buildErr error, -) ([]packer.Artifact, error) { - doneCh, ok := b.RunningBuilds[buildName] +) ([]packerSDK.Artifact, error) { + doneCh, ok := bucket.RunningBuilds[buildName] if !ok { log.Print("[ERROR] done build does not have an entry in the heartbeat table, state will be inconsistent.") @@ -572,56 +613,56 @@ func (b *Bucket) completeBuild( } if buildErr != nil { - status := models.HashicorpCloudPackerBuildStatusFAILED + status := hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDFAILED if ctx.Err() != nil { - status = models.HashicorpCloudPackerBuildStatusCANCELLED + status = hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDCANCELLED } - err := b.UpdateBuildStatus(context.Background(), buildName, status) + err := bucket.UpdateBuildStatus(context.Background(), buildName, status) if err != nil { log.Printf("[ERROR] failed to update build %q status to FAILED: %s", buildName, err) } - return artifacts, fmt.Errorf("build failed, not uploading artifacts") + return packerSDKArtifacts, fmt.Errorf("build failed, not uploading artifacts") } - for _, art := range artifacts { - var images []registryimage.Image + for _, art := range packerSDKArtifacts { + var sdkImages []packerSDKRegistry.Image decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &images, + Result: &sdkImages, WeaklyTypedInput: true, ErrorUnused: false, }) if err != nil { - return artifacts, fmt.Errorf( - "failed to create decoder for HCP Packer registry image: %w", + return packerSDKArtifacts, fmt.Errorf( + "failed to create decoder for HCP Packer artifact: %w", err) } - state := art.State(registryimage.ArtifactStateURI) + state := art.State(packerSDKRegistry.ArtifactStateURI) err = decoder.Decode(state) if err != nil { - return artifacts, fmt.Errorf( - "failed to obtain HCP Packer registry image from post-processor artifact: %w", + return packerSDKArtifacts, fmt.Errorf( + "failed to obtain HCP Packer artifact from post-processor artifact: %w", err) } log.Printf("[TRACE] updating artifacts for build %q", buildName) - err = b.UpdateImageForBuild(buildName, images...) + err = bucket.UpdateArtifactForBuild(buildName, sdkImages...) if err != nil { - return artifacts, fmt.Errorf("failed to add image artifact for %q: %s", buildName, err) + return packerSDKArtifacts, fmt.Errorf("failed to add artifact for %q: %s", buildName, err) } } - parErr := b.markBuildComplete(ctx, buildName) + parErr := bucket.markBuildComplete(ctx, buildName) if parErr != nil { - return artifacts, fmt.Errorf( - "failed to update Packer registry with image artifacts for %q: %s", + return packerSDKArtifacts, fmt.Errorf( + "failed to update HCP Packer artifacts for %q: %s", buildName, parErr) } - return append(artifacts, ®istryArtifact{ - BuildName: buildName, - BucketSlug: b.Slug, - IterationID: b.Iteration.ID, + return append(packerSDKArtifacts, ®istryArtifact{ + BuildName: buildName, + BucketName: bucket.Name, + VersionID: bucket.Version.ID, }), nil } diff --git a/internal/hcp/registry/types.bucket_service_test.go b/internal/hcp/registry/types.bucket_service_test.go index 6503b95fb02..a89233d686a 100644 --- a/internal/hcp/registry/types.bucket_service_test.go +++ b/internal/hcp/registry/types.bucket_service_test.go @@ -7,29 +7,29 @@ import ( "context" "testing" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" - "github.com/hashicorp/packer/internal/hcp/api" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + hcpPackerAPI "github.com/hashicorp/packer/internal/hcp/api" ) -func TestInitialize_NewBucketNewIteration(t *testing.T) { - mockService := api.NewMockPackerClientService() +func TestInitialize_NewBucketNewVersion(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { t.Errorf("unexpected failure: %v", err) } @@ -38,19 +38,19 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) { t.Errorf("expected a call to CreateBucket but it didn't happen") } - if !mockService.CreateIterationCalled { - t.Errorf("expected a call to CreateIteration but it didn't happen") + if !mockService.CreateVersionCalled { + t.Errorf("expected a call to CreateVersion but it didn't happen") } if mockService.CreateBuildCalled { t.Errorf("Didn't expect a call to CreateBuild") } - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") + if b.Version.ID != "version-id" { + t.Errorf("expected a version to created but it didn't") } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } @@ -58,55 +58,55 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) { t.Errorf("Expected a call to CreateBuild but it didn't happen") } - if ok := b.Iteration.HasBuild("happycloud.image"); !ok { + if ok := b.Version.HasBuild("happycloud.image"); !ok { t.Errorf("expected a basic build entry to be created but it didn't") } } func TestInitialize_UnsetTemplateTypeError(t *testing.T) { - mockService := api.NewMockPackerClientService() + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeTEMPLATETYPEUNSET) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeTEMPLATETYPEUNSET) if err == nil { t.Fatalf("unexpected success") } - t.Logf("iteration creating failed as expected: %s", err) + t.Logf("version creating failed as expected: %s", err) } -func TestInitialize_ExistingBucketNewIteration(t *testing.T) { - mockService := api.NewMockPackerClientService() +func TestInitialize_ExistingBucketNewVersion(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { t.Errorf("unexpected failure: %v", err) } @@ -115,19 +115,19 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) { t.Errorf("expected call to UpdateBucket but it didn't happen") } - if !mockService.CreateIterationCalled { - t.Errorf("expected a call to CreateIteration but it didn't happen") + if !mockService.CreateVersionCalled { + t.Errorf("expected a call to CreateVersion but it didn't happen") } if mockService.CreateBuildCalled { t.Errorf("Didn't expect a call to CreateBuild") } - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") + if b.Version.ID != "version-id" { + t.Errorf("expected a version to created but it didn't") } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } @@ -135,38 +135,38 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) { t.Errorf("Expected a call to CreateBuild but it didn't happen") } - if ok := b.Iteration.HasBuild("happycloud.image"); !ok { + if ok := b.Version.HasBuild("happycloud.image"); !ok { t.Errorf("expected a basic build entry to be created but it didn't") } } -func TestInitialize_ExistingBucketExistingIteration(t *testing.T) { - mockService := api.NewMockPackerClientService() +func TestInitialize_ExistingBucketExistingVersion(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true + mockService.VersionAlreadyExist = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { t.Errorf("unexpected failure: %v", err) } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } @@ -179,190 +179,188 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) { t.Errorf("expected call to UpdateBucket but it didn't happen") } - if mockService.CreateIterationCalled { - t.Errorf("unexpected a call to CreateIteration") + if mockService.CreateVersionCalled { + t.Errorf("unexpected a call to CreateVersion") } - if !mockService.GetIterationCalled { - t.Errorf("expected a call to GetIteration but it didn't happen") + if !mockService.GetVersionCalled { + t.Errorf("expected a call to GetVersion but it didn't happen") } if mockService.CreateBuildCalled { t.Errorf("unexpected a call to CreateBuild") } - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") + if b.Version.ID != "version-id" { + t.Errorf("expected a version to created but it didn't") } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } - existingBuild, err := b.Iteration.Build("happycloud.image") + existingBuild, err := b.Version.Build("happycloud.image") if err != nil { t.Errorf("expected the existing build loaded from an existing bucket to be valid: %v", err) } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + if existingBuild.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET { t.Errorf("expected the existing build to be in the default state") } } -func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) { - mockService := api.NewMockPackerClientService() +func TestInitialize_ExistingBucketCompleteVersion(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true - mockService.IterationCompleted = true + mockService.VersionAlreadyExist = true + mockService.VersionCompleted = true mockService.BuildAlreadyDone = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err == nil { t.Errorf("unexpected failure: %v", err) } - if mockService.CreateIterationCalled { - t.Errorf("unexpected call to CreateIteration") + if mockService.CreateVersionCalled { + t.Errorf("unexpected call to CreateVersion") } - if !mockService.GetIterationCalled { - t.Errorf("expected a call to GetIteration but it didn't happen") + if !mockService.GetVersionCalled { + t.Errorf("expected a call to GetVersion but it didn't happen") } if mockService.CreateBuildCalled { t.Errorf("unexpected call to CreateBuild") } - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to be returned but it wasn't") + if b.Version.ID != "version-id" { + t.Errorf("expected a version to be returned but it wasn't") } } func TestUpdateBuildStatus(t *testing.T) { - mockService := api.NewMockPackerClientService() + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true + mockService.VersionAlreadyExist = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { t.Errorf("unexpected failure: %v", err) } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } - existingBuild, err := b.Iteration.Build("happycloud.image") + existingBuild, err := b.Version.Build("happycloud.image") if err != nil { t.Errorf("expected the existing build loaded from an existing bucket to be valid: %v", err) } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + if existingBuild.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET { t.Errorf("expected the existing build to be in the default state") } - err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) + err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING) if err != nil { t.Errorf("unexpected failure for PublishBuildStatus: %v", err) } - existingBuild, err = b.Iteration.Build("happycloud.image") + existingBuild, err = b.Version.Build("happycloud.image") if err != nil { t.Errorf("expected the existing build loaded from an existing bucket to be valid: %v", err) } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { + if existingBuild.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING { t.Errorf("expected the existing build to be in the running state") } } func TestUpdateBuildStatus_DONENoImages(t *testing.T) { - mockService := api.NewMockPackerClientService() + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true + mockService.VersionAlreadyExist = true b := &Bucket{ - Slug: "TestBucket", - client: &api.Client{ + Name: "TestBucket", + client: &hcpPackerAPI.Client{ Packer: mockService, }, } - b.Iteration = NewIteration() - err := b.Iteration.Initialize() + b.Version = NewVersion() + err := b.Version.Initialize() if err != nil { t.Errorf("unexpected failure: %v", err) } - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - err = b.Initialize(context.TODO(), models.HashicorpCloudPackerIterationTemplateTypeHCL2) + err = b.Initialize(context.TODO(), hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) if err != nil { t.Errorf("unexpected failure: %v", err) } - err = b.populateIteration(context.TODO()) + err = b.populateVersion(context.TODO()) if err != nil { t.Errorf("unexpected failure: %v", err) } - existingBuild, err := b.Iteration.Build("happycloud.image") + existingBuild, err := b.Version.Build("happycloud.image") if err != nil { t.Errorf("expected the existing build loaded from an existing bucket to be valid: %v", err) } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + if existingBuild.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDUNSET { t.Errorf("expected the existing build to be in the default state") } //nolint:errcheck - b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) + _ = b.UpdateBuildStatus(context.TODO(), "happycloud.image", hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING) - err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusDONE) + err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE) if err == nil { t.Errorf("expected failure for PublishBuildStatus when setting status to DONE with no images") } - existingBuild, err = b.Iteration.Build("happycloud.image") + existingBuild, err = b.Version.Build("happycloud.image") if err != nil { t.Errorf("expected the existing build loaded from an existing bucket to be valid: %v", err) } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { + if existingBuild.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDRUNNING { t.Errorf("expected the existing build to be in the running state") } } - -//func (b *Bucket) PublishBuildStatus(ctx context.Context, name string, status models.HashicorpCloudPackerBuildStatus) error {} diff --git a/internal/hcp/registry/types.bucket_test.go b/internal/hcp/registry/types.bucket_test.go index 9c1467ad714..57b18d6a928 100644 --- a/internal/hcp/registry/types.bucket_test.go +++ b/internal/hcp/registry/types.bucket_test.go @@ -10,22 +10,22 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/packer/hcl2template" - "github.com/hashicorp/packer/internal/hcp/api" + hcpPackerAPI "github.com/hashicorp/packer/internal/hcp/api" ) func createInitialTestBucket(t testing.TB) *Bucket { t.Helper() - bucket := NewBucketWithIteration() - err := bucket.Iteration.Initialize() + bucket := NewBucketWithVersion() + err := bucket.Version.Initialize() if err != nil { t.Errorf("failed to initialize Bucket: %s", err) return nil } - mockService := api.NewMockPackerClientService() + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.TrackCalledServiceMethods = false - bucket.Slug = "TestBucket" - bucket.client = &api.Client{ + bucket.Name = "TestBucket" + bucket.client = &hcpPackerAPI.Client{ Packer: mockService, } @@ -42,20 +42,20 @@ func checkError(t testing.TB, err error) { t.Errorf("received an error during testing %s", err) } -func TestBucket_CreateInitialBuildForIteration(t *testing.T) { +func TestBucket_CreateInitialBuildForVersion(t *testing.T) { bucket := createInitialTestBucket(t) - componentName := "happycloud.image" + componentName := "happycloud.artifact" bucket.RegisterBuildForComponent(componentName) bucket.BuildLabels = map[string]string{ "version": "1.7.0", "based_off": "alpine", } - err := bucket.CreateInitialBuildForIteration(context.TODO(), componentName) + err := bucket.CreateInitialBuildForVersion(context.TODO(), componentName) checkError(t, err) - // Assert that a build stored on the iteration - build, err := bucket.Iteration.Build(componentName) + // Assert that a build stored on the version + build, err := bucket.Version.Build(componentName) if err != nil { t.Errorf("expected an initial build for %s to be created, but it failed", componentName) } @@ -80,12 +80,12 @@ func TestBucket_UpdateLabelsForBuild(t *testing.T) { }{ { desc: "no bucket or build specific labels", - buildName: "happcloud.image", + buildName: "happcloud.artifact", noDiffExpected: true, }, { desc: "bucket build labels", - buildName: "happcloud.image", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.0", "based_off": "alpine", @@ -95,22 +95,22 @@ func TestBucket_UpdateLabelsForBuild(t *testing.T) { }, { desc: "bucket build labels and build specific label", - buildName: "happcloud.image", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.0", "based_off": "alpine", }, buildLabels: map[string]string{ - "source_image": "another-happycloud-image", + "source_artifact": "another-happycloud-artifact", }, labelsCount: 3, noDiffExpected: false, }, { desc: "build specific label", - buildName: "happcloud.image", + buildName: "happcloud.artifact", buildLabels: map[string]string{ - "source_image": "another-happycloud-image", + "source_artifact": "another-happycloud-artifact", }, labelsCount: 1, noDiffExpected: false, @@ -129,11 +129,11 @@ func TestBucket_UpdateLabelsForBuild(t *testing.T) { bucket.BuildLabels[k] = v } - err := bucket.CreateInitialBuildForIteration(context.TODO(), componentName) + err := bucket.CreateInitialBuildForVersion(context.TODO(), componentName) checkError(t, err) - // Assert that the build is stored on the iteration - build, err := bucket.Iteration.Build(componentName) + // Assert that the build is stored on the version + build, err := bucket.Version.Build(componentName) if err != nil { t.Errorf("expected an initial build for %s to be created, but it failed", componentName) } @@ -161,31 +161,31 @@ func TestBucket_UpdateLabelsForBuild(t *testing.T) { func TestBucket_UpdateLabelsForBuild_withMultipleBuilds(t *testing.T) { bucket := createInitialTestBucket(t) - firstComponent := "happycloud.image" + firstComponent := "happycloud.artifact" bucket.RegisterBuildForComponent(firstComponent) - secondComponent := "happycloud.image2" + secondComponent := "happycloud.artifact2" bucket.RegisterBuildForComponent(secondComponent) - err := bucket.populateIteration(context.TODO()) + err := bucket.populateVersion(context.TODO()) checkError(t, err) err = bucket.UpdateLabelsForBuild(firstComponent, map[string]string{ - "source_image": "another-happycloud-image", + "source_artifact": "another-happycloud-artifact", }) checkError(t, err) err = bucket.UpdateLabelsForBuild(secondComponent, map[string]string{ - "source_image": "the-original-happycloud-image", - "role_name": "no-role-is-a-good-role", + "source_artifact": "the-original-happycloud-artifact", + "role_name": "no-role-is-a-good-role", }) checkError(t, err) var registeredBuilds []*Build expectedComponents := []string{firstComponent, secondComponent} for _, componentName := range expectedComponents { - // Assert that a build stored on the iteration - build, err := bucket.Iteration.Build(componentName) + // Assert that a build stored on the version + build, err := bucket.Version.Build(componentName) if err != nil { t.Errorf("expected an initial build for %s to be created, but it failed", componentName) } @@ -209,7 +209,7 @@ func TestBucket_UpdateLabelsForBuild_withMultipleBuilds(t *testing.T) { } } -func TestBucket_PopulateIteration(t *testing.T) { +func TestBucket_PopulateVersion(t *testing.T) { tc := []struct { desc string buildName string @@ -220,15 +220,15 @@ func TestBucket_PopulateIteration(t *testing.T) { noDiffExpected bool }{ { - desc: "populating iteration with existing incomplete build and no bucket build labels does nothing", - buildName: "happcloud.image", + desc: "populating version with existing incomplete build and no bucket build labels does nothing", + buildName: "happcloud.artifact", labelsCount: 0, buildCompleted: false, noDiffExpected: true, }, { - desc: "populating iteration with existing incomplete build should add bucket build labels", - buildName: "happcloud.image", + desc: "populating version with existing incomplete build should add bucket build labels", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.0", "based_off": "alpine", @@ -238,8 +238,8 @@ func TestBucket_PopulateIteration(t *testing.T) { noDiffExpected: true, }, { - desc: "populating iteration with existing incomplete build should update bucket build labels", - buildName: "happcloud.image", + desc: "populating version with existing incomplete build should update bucket build labels", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.3", "based_off": "alpine-3.14", @@ -253,8 +253,8 @@ func TestBucket_PopulateIteration(t *testing.T) { noDiffExpected: true, }, { - desc: "populating iteration with completed build should not modify any labels", - buildName: "happcloud.image", + desc: "populating version with completed build should not modify any labels", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.0", "based_off": "alpine", @@ -264,8 +264,8 @@ func TestBucket_PopulateIteration(t *testing.T) { noDiffExpected: false, }, { - desc: "populating iteration with existing build should only modify bucket build labels", - buildName: "happcloud.image", + desc: "populating version with existing build should only modify bucket build labels", + buildName: "happcloud.artifact", bucketBuildLabels: map[string]string{ "version": "1.7.3", "based_off": "alpine-3.14", @@ -285,39 +285,39 @@ func TestBucket_PopulateIteration(t *testing.T) { t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "test-run-"+strconv.Itoa(i)) - mockService := api.NewMockPackerClientService() + mockService := hcpPackerAPI.NewMockPackerClientService() mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true + mockService.VersionAlreadyExist = true mockService.BuildAlreadyDone = tt.buildCompleted - bucket := NewBucketWithIteration() - err := bucket.Iteration.Initialize() + bucket := NewBucketWithVersion() + err := bucket.Version.Initialize() if err != nil { - t.Fatalf("failed when calling NewBucketWithIteration: %s", err) + t.Fatalf("failed when calling NewBucketWithVersion: %s", err) } - bucket.Slug = "TestBucket" - bucket.client = &api.Client{ + bucket.Name = "TestBucket" + bucket.client = &hcpPackerAPI.Client{ Packer: mockService, } for k, v := range tt.bucketBuildLabels { bucket.BuildLabels[k] = v } - componentName := "happycloud.image" + componentName := "happycloud.artifact" bucket.RegisterBuildForComponent(componentName) mockService.ExistingBuilds = append(mockService.ExistingBuilds, componentName) mockService.ExistingBuildLabels = tt.buildLabels - err = bucket.populateIteration(context.TODO()) + err = bucket.populateVersion(context.TODO()) checkError(t, err) if mockService.CreateBuildCalled { t.Errorf("expected an initial build for %s to already exist, but it called CreateBuild", componentName) } - // Assert that a build stored on the iteration - build, err := bucket.Iteration.Build(componentName) + // Assert that a build stored on the version + build, err := bucket.Version.Build(componentName) if err != nil { t.Errorf("expected an existing build for %s to be stored, but it failed", componentName) } @@ -360,7 +360,7 @@ func TestReadFromHCLBuildBlock(t *testing.T) { }, }, expectedBucket: &Bucket{ - Slug: "hcp_packer_registry-block-test", + Name: "hcp_packer_registry-block-test", Description: "description from hcp_packer_registry block", BucketLabels: map[string]string{ "org": "test", diff --git a/internal/hcp/registry/types.builds.go b/internal/hcp/registry/types.builds.go index 991ef563a00..a55e9b0b9f6 100644 --- a/internal/hcp/registry/types.builds.go +++ b/internal/hcp/registry/types.builds.go @@ -6,41 +6,41 @@ package registry import ( "fmt" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" - registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + packerSDKRegistry "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" ) // Build represents a build of a given component type for some bucket on the HCP Packer Registry. type Build struct { ID string - CloudProvider string + Platform string ComponentType string RunUUID string Labels map[string]string - Images map[string]registryimage.Image - Status models.HashicorpCloudPackerBuildStatus + Artifacts map[string]packerSDKRegistry.Image + Status hcpPackerModels.HashicorpCloudPacker20230101BuildStatus } -// NewBuildFromCloudPackerBuild converts a HashicorpCloudePackerBuild to a local build that can be tracked and published to the HCP Packer Registry. -// Any existing labels or images associated to src will be copied to the returned Build. -func NewBuildFromCloudPackerBuild(src *models.HashicorpCloudPackerBuild) (*Build, error) { +// NewBuildFromCloudPackerBuild converts a HashicorpCloudPackerBuild to a local build that can be tracked and +// published to the HCP Packer. +// Any existing labels or artifacts associated to src will be copied to the returned Build. +func NewBuildFromCloudPackerBuild(src *hcpPackerModels.HashicorpCloudPacker20230101Build) (*Build, error) { build := Build{ ID: src.ID, ComponentType: src.ComponentType, - CloudProvider: src.CloudProvider, + Platform: src.Platform, RunUUID: src.PackerRunUUID, Status: *src.Status, Labels: src.Labels, } var err error - for _, image := range src.Images { - image := image - err = build.AddImages(registryimage.Image{ - ImageID: image.ImageID, - ProviderName: build.CloudProvider, - ProviderRegion: image.Region, + for _, artifact := range src.Artifacts { + err = build.AddArtifacts(packerSDKRegistry.Image{ + ImageID: artifact.ExternalIdentifier, + ProviderName: build.Platform, + ProviderRegion: artifact.Region, }) if err != nil { @@ -51,58 +51,56 @@ func NewBuildFromCloudPackerBuild(src *models.HashicorpCloudPackerBuild) (*Build return &build, nil } -// AddLabelsToBuild merges the contents of data to the labels associated with the build. +// MergeLabels merges the contents of data to the labels associated with the build. // Duplicate keys will be updated to reflect the new value. -func (b *Build) MergeLabels(data map[string]string) { +func (build *Build) MergeLabels(data map[string]string) { if data == nil { return } - if b.Labels == nil { - b.Labels = make(map[string]string) + if build.Labels == nil { + build.Labels = make(map[string]string) } for k, v := range data { // TODO: (nywilken) Determine why we skip labels already set - //if _, ok := build.Labels[k]; ok { - //continue - //} - b.Labels[k] = v + // if _, ok := build.Labels[k]; ok { + // continue + // } + build.Labels[k] = v } } -// AddImages appends one or more images artifacts to the build. -func (b *Build) AddImages(images ...registryimage.Image) error { +// AddArtifacts appends one or more artifacts to the build. +func (build *Build) AddArtifacts(artifacts ...packerSDKRegistry.Image) error { - if b.Images == nil { - b.Images = make(map[string]registryimage.Image) + if build.Artifacts == nil { + build.Artifacts = make(map[string]packerSDKRegistry.Image) } - for _, image := range images { - image := image - - if err := image.Validate(); err != nil { - return fmt.Errorf("AddImages: failed to add image to build %q: %w", b.ComponentType, err) + for _, artifact := range artifacts { + if err := artifact.Validate(); err != nil { + return fmt.Errorf("AddArtifacts: failed to add artifact to build %q: %w", build.ComponentType, err) } - if b.CloudProvider == "" { - b.CloudProvider = image.ProviderName + if build.Platform == "" { + build.Platform = artifact.ProviderName } - b.MergeLabels(image.Labels) - b.Images[image.String()] = image + build.MergeLabels(artifact.Labels) + build.Artifacts[artifact.String()] = artifact } return nil } // IsNotDone returns true if build does not satisfy all requirements of a completed build. -// A completed build must have a valid ID, one or more Images, and its Status is HashicorpCloudPackerBuildStatusDONE. -func (b *Build) IsNotDone() bool { - hasBuildID := b.ID != "" - hasNoImages := len(b.Images) == 0 - isNotDone := b.Status != models.HashicorpCloudPackerBuildStatusDONE +// A completed build must have a valid ID, one or more Artifacts, and its Status is HashicorpCloudPacker20230101BuildStatusBUILDDONE. +func (build *Build) IsNotDone() bool { + hasBuildID := build.ID != "" + hasNoArtifacts := len(build.Artifacts) == 0 + isNotDone := build.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE - return hasBuildID && hasNoImages && isNotDone + return hasBuildID && hasNoArtifacts && isNotDone } diff --git a/internal/hcp/registry/types.iterations.go b/internal/hcp/registry/types.iterations.go deleted file mode 100644 index c186a2a256d..00000000000 --- a/internal/hcp/registry/types.iterations.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package registry - -import ( - "errors" - "fmt" - "math/rand" - "os" - "strings" - "sync" - "time" - - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models" - sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" - registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" - "github.com/hashicorp/packer/internal/hcp/env" - "github.com/oklog/ulid" -) - -type Iteration struct { - ID string - AncestorSlug string - Fingerprint string - RunUUID string - builds sync.Map - expectedBuilds []string -} - -type IterationOptions struct { - TemplateBaseDir string -} - -// NewIteration returns a pointer to an Iteration that can be used for storing Packer build details needed by PAR. -func NewIteration() *Iteration { - i := Iteration{ - expectedBuilds: make([]string, 0), - } - - return &i -} - -// Initialize prepares the iteration to be used with an active HCP Packer registry bucket. -func (i *Iteration) Initialize() error { - if i == nil { - return errors.New("Unexpected call to initialize for a nil Iteration") - } - - // By default we try to load a Fingerprint from the environment variable. - // If no variable is defined we generate a new fingerprint. - i.Fingerprint = os.Getenv(env.HCPPackerBuildFingerprint) - - if i.Fingerprint != "" { - return nil - } - - fp, err := ulid.New(ulid.Now(), ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)) - if err != nil { - return fmt.Errorf("Failed to generate a fingerprint: %s", err) - } - i.Fingerprint = fp.String() - - return nil -} - -// StoreBuild stores a build for buildName to an active iteration. -func (i *Iteration) StoreBuild(buildName string, build *Build) { - i.builds.Store(buildName, build) -} - -// Build gets the store build associated with buildName in the active iteration. -func (i *Iteration) Build(buildName string) (*Build, error) { - build, ok := i.builds.Load(buildName) - if !ok { - return nil, errors.New("no associated build found for the name " + buildName) - } - - b, ok := build.(*Build) - if !ok { - return nil, fmt.Errorf("the build for the component %q does not appear to be a valid registry Build", buildName) - } - - return b, nil -} - -// HasBuild checks if iteration has a stored build associated with buildName. -func (i *Iteration) HasBuild(buildName string) bool { - _, ok := i.builds.Load(buildName) - - return ok -} - -// AddImageToBuild appends one or more images artifacts to the build referred to by buildName. -func (i *Iteration) AddImageToBuild(buildName string, images ...registryimage.Image) error { - build, err := i.Build(buildName) - if err != nil { - return fmt.Errorf("AddImageToBuild: %w", err) - } - - err = build.AddImages(images...) - if err != nil { - return fmt.Errorf("AddImageToBuild: %w", err) - } - - i.StoreBuild(buildName, build) - return nil -} - -// AddLabelsToBuild merges the contents of data to the labels associated with the build referred to by buildName. -func (i *Iteration) AddLabelsToBuild(buildName string, data map[string]string) error { - build, err := i.Build(buildName) - if err != nil { - return fmt.Errorf("AddLabelsToBuild: %w", err) - } - - build.MergeLabels(data) - - i.StoreBuild(buildName, build) - return nil -} - -// AddSHAToBuildLabels adds the Git SHA for the current iteration (if set) as a label for all the builds of the iteration -func (i *Iteration) AddSHAToBuildLabels(sha string) { - i.builds.Range(func(_, v any) bool { - b, ok := v.(*Build) - if !ok { - return true - } - - b.MergeLabels(map[string]string{ - "git_sha": sha, - }) - - return true - }) -} - -// RemainingBuilds returns the list of builds that are not in a DONE status -func (i *Iteration) RemainingBuilds() []*Build { - var todo []*Build - - i.builds.Range(func(k, v any) bool { - build, ok := v.(*Build) - if !ok { - // Unlikely since the builds map contains only Build instances - return true - } - - if build.Status != models.HashicorpCloudPackerBuildStatusDONE { - todo = append(todo, build) - } - return true - }) - - return todo -} - -func (i *Iteration) iterationStatusSummary(ui sdkpacker.Ui) { - rem := i.RemainingBuilds() - if rem == nil { - return - } - - buf := &strings.Builder{} - - buf.WriteString(fmt.Sprintf( - "\nIteration %q is not complete, the following builds are not done:\n\n", - i.Fingerprint)) - for _, b := range rem { - buf.WriteString(fmt.Sprintf("* %q: %s\n", b.ComponentType, b.Status)) - } - buf.WriteString("\nYou may resume work on this iteration in further Packer builds by defining the following variable in your environment:\n") - buf.WriteString(fmt.Sprintf("HCP_PACKER_BUILD_FINGERPRINT=%q", i.Fingerprint)) - - ui.Say(buf.String()) -} diff --git a/internal/hcp/registry/types.version.go b/internal/hcp/registry/types.version.go new file mode 100644 index 00000000000..391717cc0c7 --- /dev/null +++ b/internal/hcp/registry/types.version.go @@ -0,0 +1,176 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "errors" + "fmt" + "math/rand" + "os" + "strings" + "sync" + "time" + + hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" + sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" + packerSDKRegistry "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" + "github.com/hashicorp/packer/internal/hcp/env" + "github.com/oklog/ulid" +) + +type Version struct { + ID string + Fingerprint string + RunUUID string + builds sync.Map + expectedBuilds []string +} + +type VersionOptions struct { + TemplateBaseDir string +} + +// NewVersion returns a pointer to a Version that can be used for storing Packer build details needed. +func NewVersion() *Version { + i := Version{ + expectedBuilds: make([]string, 0), + } + + return &i +} + +// Initialize prepares the version to be used with HCP Packer. +func (version *Version) Initialize() error { + if version == nil { + return errors.New("Unexpected call to initialize for a nil Version") + } + + // Bydefault we try to load a Fingerprint from the environment variable. + // If no variable is defined we generate a new fingerprint. + version.Fingerprint = os.Getenv(env.HCPPackerBuildFingerprint) + + if version.Fingerprint != "" { + return nil + } + + fp, err := ulid.New(ulid.Now(), ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)) + if err != nil { + return fmt.Errorf("Failed to generate a fingerprint: %s", err) + } + version.Fingerprint = fp.String() + + return nil +} + +// StoreBuild stores a build for buildName to an active version. +func (version *Version) StoreBuild(buildName string, build *Build) { + version.builds.Store(buildName, build) +} + +// Build gets the store build associated with buildName in the active version. +func (version *Version) Build(buildName string) (*Build, error) { + build, ok := version.builds.Load(buildName) + if !ok { + return nil, errors.New("no associated build found for the name " + buildName) + } + + b, ok := build.(*Build) + if !ok { + return nil, fmt.Errorf("the build for the component %q does not appear to be a valid registry Build", buildName) + } + + return b, nil +} + +// HasBuild checks if version has a stored build associated with buildName. +func (version *Version) HasBuild(buildName string) bool { + _, ok := version.builds.Load(buildName) + + return ok +} + +// AddArtifactToBuild appends one or more artifacts to the build referred to by buildName. +func (version *Version) AddArtifactToBuild(buildName string, artifacts ...packerSDKRegistry.Image) error { + build, err := version.Build(buildName) + if err != nil { + return fmt.Errorf("AddArtifactToBuild: %w", err) + } + + err = build.AddArtifacts(artifacts...) + if err != nil { + return fmt.Errorf("AddArtifactToBuild: %w", err) + } + + version.StoreBuild(buildName, build) + return nil +} + +// AddLabelsToBuild merges the contents of data to the labels associated with the build referred to by buildName. +func (version *Version) AddLabelsToBuild(buildName string, data map[string]string) error { + build, err := version.Build(buildName) + if err != nil { + return fmt.Errorf("AddLabelsToBuild: %w", err) + } + + build.MergeLabels(data) + + version.StoreBuild(buildName, build) + return nil +} + +// AddSHAToBuildLabels adds the Git SHA for the current version (if set) as a label for all the builds of the version +func (version *Version) AddSHAToBuildLabels(sha string) { + version.builds.Range(func(_, v any) bool { + b, ok := v.(*Build) + if !ok { + return true + } + + b.MergeLabels(map[string]string{ + "git_sha": sha, + }) + + return true + }) +} + +// RemainingBuilds returns the list of builds that are not in a DONE status +func (version *Version) RemainingBuilds() []*Build { + var todo []*Build + + version.builds.Range(func(k, v any) bool { + build, ok := v.(*Build) + if !ok { + // Unlikely since the builds map contains only Build instances + return true + } + + if build.Status != hcpPackerModels.HashicorpCloudPacker20230101BuildStatusBUILDDONE { + todo = append(todo, build) + } + return true + }) + + return todo +} + +func (version *Version) statusSummary(ui sdkpacker.Ui) { + rem := version.RemainingBuilds() + if rem == nil { + return + } + + buf := &strings.Builder{} + + buf.WriteString(fmt.Sprintf( + "\nVersion %q is not complete, the following builds are not done:\n\n", + version.Fingerprint)) + for _, b := range rem { + buf.WriteString(fmt.Sprintf("* %q: %s\n", b.ComponentType, b.Status)) + } + buf.WriteString("\nYou may resume work on this version in further Packer builds by defining the following variable in your environment:\n") + buf.WriteString(fmt.Sprintf("HCP_PACKER_BUILD_FINGERPRINT=%q", version.Fingerprint)) + + ui.Say(buf.String()) +} diff --git a/internal/hcp/registry/types.iterations_test.go b/internal/hcp/registry/types.version_test.go similarity index 88% rename from internal/hcp/registry/types.iterations_test.go rename to internal/hcp/registry/types.version_test.go index 27e87b5798f..ab6b0e122c7 100644 --- a/internal/hcp/registry/types.iterations_test.go +++ b/internal/hcp/registry/types.version_test.go @@ -8,10 +8,10 @@ import ( "path" "testing" - git "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5" ) -func TestIteration_Initialize(t *testing.T) { +func TestVersion_Initialize(t *testing.T) { var tc = []struct { name string fingerprint string @@ -56,7 +56,7 @@ func TestIteration_Initialize(t *testing.T) { tt.setupFn(t) } - i := NewIteration() + i := NewVersion() err := i.Initialize() if tt.errorExpected { t.Logf("%v", err) @@ -65,7 +65,7 @@ func TestIteration_Initialize(t *testing.T) { } if i.Fingerprint != "" { - t.Errorf("expected %q to result in an error with an empty iteration fingerprint, but got %q", tt.name, i.Fingerprint) + t.Errorf("expected %q to result in an error with an empty version fingerprint, but got %q", tt.name, i.Fingerprint) } return } diff --git a/website/content/docs/datasources/hcp/hcp-packer-artifact.mdx b/website/content/docs/datasources/hcp/hcp-packer-artifact.mdx new file mode 100644 index 00000000000..0261242a879 --- /dev/null +++ b/website/content/docs/datasources/hcp/hcp-packer-artifact.mdx @@ -0,0 +1,105 @@ +--- +description: | + The HCP Packer Artifact Data Source retrieves information about an + artifact from the HCP Packer Registry. This information can be used to + provide a source artifact to various Packer builders. +page_title: HCP Packer Artifact - Data Sources +--- + + + + + + +# HCP Packer Artifact Data Source + +Type: `hcp-packer-artifact` + +The `HCP Packer Artifact` Data Source retrieves information about an +artifact from the HCP Packer Registry. This information can be used to +provide a source artifact to various Packer builders. + +To get started with HCP Packer, refer to the [HCP Packer documentation](/hcp/docs/packer) or try +the [Get Started with HCP Packer tutorials](/packer/tutorials/hcp-get-started). + +~> **Note:** You will receive an error if you try to reference metadata from a deactivated or deleted registry. +An administrator can manually deactivate or delete a registry, and HCP Packer automatically deactivates registries +with billing issues. Contact [HashiCorp Support](https://support.hashicorp.com/) with questions. + +## Revoked Versions + +If an HCP Packer Version is revoked, the `hcp-packer-version` data source will fail and Packer won't proceed with +the build. Building new artifacts from a revoked artifact is not compliant. +Versions that are scheduled to be revoked will still be considered valid until the revocation date. + +## Basic Example + +Below is a fully functioning example. It stores information about an image artifact, +which can then be parsed and accessed as a variable. + +```hcl +data "hcp-packer-artifact" "example" { + bucket_name = "hardened-ubuntu-16-04" + version_fingerprint = "${data.hcp-packer-version.hardened-source.fingerprint}" + platform = "aws" + region = "us-east-1" +} +``` + +## Full Example + +This data source can be used in conjunction with the hcp-packer-version +data source to retrieve a version fingerprint using a channel. You provide the version fingerprint and channel +name to the version data source, then use the version source inside the +artifact data source, then use the artifact data source inside your source block. + +```hcl +# Retrieves information about the HCP Packer Version; a "version" can be +# thought of as all the metadata created by a single call of `packer build`. +data "hcp-packer-version" "hardened-source" { + bucket_name = "hardened-ubuntu-16-04" + channel_name = "dev" +} + +# Retrieves information about the HCP Packer Artifact; an artifact can be thought +# of as all the metadata (including the artifact names) created by a single +# "source" builder; this can include multiple artifacts so we provide a +# region to disambiguate. +data "hcp-packer-artifact" "example" { + bucket_name = "hardened-ubuntu-16-04" + version_fingerprint = data.hcp_packer_version.hardened-source.fingerprint + platform = "aws" + region = "us-east-1" +} + +# This source uses the output from a previous Packer build. By using the +# HCP Packer Registry in this way, you can easily create build pipelines where +# a single base artifact can be customized in multiple secondary layers. +source "amazon-ebs" "packer-secondary" { + source_ami = data.hcp-packer-artifact.example.external_identifier + ... +} +``` + +## Configuration Reference + +Configuration options are organized below into two categories: required and +optional. Within each category, the available options are alphabetized and +described. + +### Required: + +@include 'datasource/hcp-packer-artifact/Config-required.mdx' + +### Optional: + +~> **Note:** This data source only returns the first found artifact's metadata filtered by the given options, +from the returned list of artifacts associated with the specified version. Therefore, if multiple artifacts exist +in the same region, it will only pick one of them. In this case, you can filter artifact by a source build name +(Ex: `amazon-ebs.example`) using the `component_type` option. + +@include 'datasource/hcp-packer-artifact/Config-not-required.mdx' + +### Output Fields: + +@include 'datasource/hcp-packer-artifact/DatasourceOutput.mdx' diff --git a/website/content/docs/datasources/hcp/hcp-packer-image.mdx b/website/content/docs/datasources/hcp/hcp-packer-image.mdx index 8e1bf822337..d7312921cb4 100644 --- a/website/content/docs/datasources/hcp/hcp-packer-image.mdx +++ b/website/content/docs/datasources/hcp/hcp-packer-image.mdx @@ -1,5 +1,6 @@ --- description: | + This data source has been deprecated, please use HCP Packer Artifact data source instead. The HCP Packer Image Data Source retrieves information about an image from the HCP Packer registry. This information can be used to provide a source image to various Packer builders. @@ -8,11 +9,12 @@ page_title: HCP Packer Image - Data Sources - # HCP Packer Image Data Source +~> **Note:** This data source has been deprecated, please use [HCP Packer Artifact](/packer/docs/datasources/hcp/hcp-packer-artifact) data source instead. + Type: `hcp-packer-image` The `HCP Packer Image` Data Source retrieves information about an diff --git a/website/content/docs/datasources/hcp/hcp-packer-iteration.mdx b/website/content/docs/datasources/hcp/hcp-packer-iteration.mdx index 38c6125baee..6ad6fb3c396 100644 --- a/website/content/docs/datasources/hcp/hcp-packer-iteration.mdx +++ b/website/content/docs/datasources/hcp/hcp-packer-iteration.mdx @@ -1,5 +1,6 @@ --- description: | + This data source has been deprecated, please use HCP Packer Version data source instead. The HCP Packer Iteration Data Source retrieves information about an iteration from the HCP Packer registry. This information can be used to query HCP for a source image for various Packer builders. @@ -8,11 +9,12 @@ page_title: HCP Packer Iteration - Data Sources - # HCP Packer Iteration Data Source +~> **Note:** This data source has been deprecated, please use [HCP Packer Version](/packer/docs/datasources/hcp/hcp-packer-version) data source instead. + Type: `hcp-packer-iteration` The `HCP Packer Iteration` Data Source retrieves information about an diff --git a/website/content/docs/datasources/hcp/hcp-packer-version.mdx b/website/content/docs/datasources/hcp/hcp-packer-version.mdx new file mode 100644 index 00000000000..31b288ca4a0 --- /dev/null +++ b/website/content/docs/datasources/hcp/hcp-packer-version.mdx @@ -0,0 +1,96 @@ +--- +description: | + The HCP Packer Version Data Source retrieves information about + HCP Packer Version from the HCP Packer Registry. This information can be used to + query HCP for a source external identifier for various Packer builders. +page_title: HCP Packer Version - Data Sources +--- + + + + + + +# HCP Packer Version Data Source + +Type: `hcp-packer-version` + +The `HCP Packer Version` Data Source retrieves information about +HCP Packer Version from the HCP Packer Registry. This information can be used to +query HCP for a source external identifier for various Packer builders. + +To get started with HCP Packer, refer to the [HCP Packer documentation](/hcp/docs/packer) or try the +[Get Started with HCP Packer tutorials](/packer/tutorials/hcp-get-started). + +~> **Note:** You will receive an error if you try to reference metadata from a deactivated or deleted registry. +An administrator can manually deactivate or delete a registry, and HCP Packer automatically deactivates registries +with billing issues. Contact [HashiCorp Support](https://support.hashicorp.com/) with questions. + +## Revoked Versions + +If an HCP Packer Version is revoked, the `hcp-packer-version` data source will fail and Packer won't proceed with +the build. Building new artifacts from a revoked artifact is not compliant. +Versions that are scheduled to be revoked will still be considered valid until the revocation date. + +## Basic Example + +Below is a fully functioning example. It stores information about an HCP Packer Version, which can then be accessed as a variable. + +```hcl +data "hcp-packer-version" "hardened-source" { + bucket_name = "hardened-ubuntu-16-04" + channel_name = "dev" +} +``` + +## Full Example + +This data source can be used in conjunction with the `hcp-packer-artifact` +data source to retrieve an artifact identifier. You provide the version fingerprint and channel +name to the version data source, then use the version source inside the +artifact data source, then use the artifact data source inside your source block. + +```hcl +# Retrieves information about the HCP Packer Version; a "version" can be +# thought of as all the metadata created by a single call of `packer build`. +data "hcp-packer-version" "hardened-source" { + bucket_name = "hardened-ubuntu-16-04" + channel_name = "dev" +} + +# Retrieves information about the HCP Packer Artifact; an artifact can be thought +# of as all the metadata (including the artifact names) created by a single +# "source" builder; this can include multiple artifacts so we provide a +# region to disambiguate. +data "hcp-packer-artifact" "example" { + bucket_name = "hardened-ubuntu-16-04" + version_fingerprint = data.hcp_packer_version.hardened-source.fingerprint + platform = "aws" + region = "us-east-1" +} + +# This source uses the output from a previous Packer build. By using the +# HCP Packer Registry in this way, you can easily create build pipelines where +# a single base artifact can be customized in multiple secondary layers. +source "amazon-ebs" "packer-secondary" { + source_ami = data.hcp-packer-artifact.example.external_identifier + ... +} +``` + +## Configuration Reference + +Configuration options are organized below into two categories: required and +optional. Within each category, the available options are alphabetized and +described. + +### Required: + +@include 'datasource/hcp-packer-version/Config-required.mdx' + +There are currently no optional fields for this datasource, though we intend +to add filtering fields in the future. + +### Output Fields: + +@include 'datasource/hcp-packer-version/DatasourceOutput.mdx' diff --git a/website/content/docs/datasources/hcp/index.mdx b/website/content/docs/datasources/hcp/index.mdx index 989d3d861a1..7f5fff7616a 100644 --- a/website/content/docs/datasources/hcp/index.mdx +++ b/website/content/docs/datasources/hcp/index.mdx @@ -11,31 +11,37 @@ sidebar_title: Overview # HCP Packer Registry Data sources -The HCP Packer registry bridges the gap between image factories and image +The HCP Packer Registry bridges the gap between artifact factories and artifact deployments, allowing development and security teams to work together to create, -manage, and consume images in a centralized way. - -The HCP Packer registry stores metadata about your images, including when they -were created, where the image exists in the cloud, and what (if any) git commit -is associated with your image build. You can use the registry to track -information about the golden images your Packer builds produce, clearly -designate which images are appropriate for test and production environments, -and query for the right golden images to use in both Packer and Terraform +manage, and consume artifacts in a centralized way. + +The HCP Packer Registry stores metadata about your artifacts, including when they +were created, where the artifacts exists in the cloud, and what (if any) git commit +is associated with your build. You can use the registry to track +information about the artifacts your Packer builds produce, clearly +designate which artifacts are appropriate for test and production environments, +and query for the right artifacts to use in both Packer and Terraform configurations. Packer has two data sources that work together to retrieve information from the HCP Packer registry: +- [hcp-packer-version](/packer/docs/datasources/hcp/hcp-packer-version) - +retrieves information about an HCP Packer Version in HCP Packer Registry +- [hcp-packer-artifact](/packer/docs/datasources/hcp/hcp-packer-artifact) - retrieves +information about a specific artifact created in the HCP Packer registry + +Deprecated data sources: (Please use above given data sources instead) - [hcp-packer-iteration](/packer/docs/datasources/hcp/hcp-packer-iteration) - retrieves information about an iteration in HCP Packer registry - [hcp-packer-image](/packer/docs/datasources/hcp/hcp-packer-image) - retrieves information about a specific image created in the HCP Packer registry -These data sources are intended to be used together to determine source images +These data sources are intended to be used together to determine source artifact for pipelined Packer builds. ## How to use this plugin This plugin comes bundled with the Packer core, so you do not need to install it separately. Please install Packer v1.7.7 or above to use the latest version -of the HCP Packer registry datasources. +of the HCP Packer Registry data sources. diff --git a/website/content/docs/hcp/index.mdx b/website/content/docs/hcp/index.mdx index d908cca70cc..8063bd0dcc7 100644 --- a/website/content/docs/hcp/index.mdx +++ b/website/content/docs/hcp/index.mdx @@ -1,72 +1,120 @@ --- description: | - Packer can publish metadata for completed builds to an HCP Packer registry. Legacy JSON templates can connect to the registry using environment variables. HCL2 templates can connect using an hcp_packer_registry block. + Packer can publish metadata for completed builds to an HCP Packer Registry. Legacy JSON templates can connect to the registry using environment variables. HCL2 templates can connect using an hcp_packer_registry block. page_title: HCP Packer --- --> **Note:** On May 16th 2023, HCP introduced multi-project support to the platform. In order to use multiple projects in your organization, you will need to update Packer to version 1.9.1 or above. Starting with 1.9.1, you may specify a project ID to push builds to with the `HCP_PROJECT_ID` environment variable. If no project ID is specified, Packer will pick the project with the oldest creation date. Older versions of Packer are incompatible with multi-project support on HCP, and builds will fail for HCP organizations with multiple projects on versions before 1.9.1. +-> **Note:** On May 16th 2023, HCP introduced multi-project support to the platform. In order to use multiple projects +in your organization, you will need to update Packer to version 1.9.1 or above. Starting with 1.9.1, you may specify +a project ID to push builds to with the `HCP_PROJECT_ID` environment variable. If no project ID is specified, +Packer will pick the project with the oldest creation date. Older versions of Packer are incompatible with multi-project +support on HCP, and builds will fail for HCP organizations with multiple projects on versions before 1.9.1. # HCP Packer -The HCP Packer registry bridges the gap between image factories and image deployments, allowing development and security teams to work together to create, manage, and consume images in a centralized way. +The HCP Packer registry bridges the gap between artifact factories and artifact deployments, allowing development and +security teams to work together to create, manage, and consume artifacts in a centralized way. -The HCP Packer registry stores metadata about your images, including when they were created, where the image exists in the cloud, and what (if any) git commit is associated with your image build. You can use the registry to track information about the golden images your Packer builds produce, clearly designate which images are appropriate for test and production environments, and query for the right golden images to use in both Packer and Terraform configurations. +The HCP Packer Registry stores metadata about your artifact, including when they were created, where the artifact +exists on the external platform, and what (if any) git commit is associated with your build. You can use the registry +to track information about the artifact your Packer builds produce, clearly designate which artifact are appropriate +for test and production environments, and query for the right artifact to use in both Packer and Terraform +configurations. -You can use HCP Packer with both JSON and HCL2 templates. If you are using JSON templates, we recommend getting started with -the [HCP Packer environment variables](#hcp-packer-environment-variables) and then migrating to HCL when possible. +You can use HCP Packer with both JSON and HCL2 templates. If you are using JSON templates, we recommend getting started with +the [HCP Packer environment variables](#hcp-packer-environment-variables) and then migrating to HCL when possible. -This page summarizes the methods you can use to connect JSON and HCL2 templates to the HCP Packer registry. It also provides a full list of HCP Packer environment variables. Refer to the [Packer Template Configuration](/hcp/docs/packer/store-image-metadata/packer-template-configuration) page in the HCP Packer documentation for full configuration details and examples. +This page summarizes the methods you can use to connect JSON and HCL2 templates to the HCP Packer registry. It also +provides a full list of HCP Packer environment variables. Refer to the +[Packer Template Configuration](/hcp/docs/packer/store-image-metadata/packer-template-configuration) page in the HCP +Packer documentation for full configuration details and examples. ### HCP Packer Environment Variables -The following environment variables let you configure Packer to push image metadata to an active registry without changing your template. You can use environment variables with both JSON and HCL2 templates. Refer to [Basic Configuration With Environment Variables](/hcp/docs/packer/store-image-metadata/packer-template-configuration#basic-configuration-with-environment-variables) in the HCP Packer documentation for complete instructions and examples. +The following environment variables let you configure Packer to push artifact metadata to an active registry without +changing your template. You can use environment variables with both JSON and HCL2 templates. +Refer to [Basic Configuration With Environment Variables](/hcp/docs/packer/store-image-metadata/packer-template-configuration#basic-configuration-with-environment-variables) +in the HCP Packer documentation for complete instructions and examples. You must set the following environment variables to enable Packer to push metadata to a registry. -- `HCP_CLIENT_ID` - The HCP client ID of a HashiCorp Cloud Platform service principle that Packer can use to authenticate to an HCP Packer registry. +- `HCP_CLIENT_ID` - The HCP client ID of a HashiCorp Cloud Platform service principle that Packer can use to +authenticate to an HCP Packer Registry. -- `HCP_CLIENT_SECRET` - The HCP client secret of the HashiCorp Cloud Platform service principle that Packer can use to authenticate to an HCP Packer registry. +- `HCP_CLIENT_SECRET` - The HCP client secret of the HashiCorp Cloud Platform service principle that Packer +can use to authenticate to an HCP Packer Registry. -- `HCP_PACKER_BUCKET_NAME` - The name of the image bucket where you want HCP Packer to store image metadata from builds associated with your template. HCP Packer automatically creates the image bucket if it does not already exist. If your HCL2 template contains an `hcp_packer_registry` block, the bucket name specified in the configuration will be overwritten by this environment variable. +- `HCP_PACKER_BUCKET_NAME` - The name of the HCP Packer Bucket where you want HCP Packer to store artifact metadata +from builds associated with your template. HCP Packer automatically creates the bucket if it does not already exist. +If your HCL2 template contains an `hcp_packer_registry` block, the bucket name specified in the configuration will be +overwritten by this environment variable. You can set these additional environment variables to control how metadata is pushed to the registry. -- `HCP_PACKER_BUILD_FINGERPRINT` - A unique identifier assigned to each iteration. To reuse a fingerprint that is associated with an existing incomplete iteration you must set this environment variable. Refer to [Iteration Fingerprinting](#iteration-fingerprinting) for usage details. +- `HCP_PACKER_BUILD_FINGERPRINT` - A unique identifier assigned to each version. To reuse a fingerprint that is +associated with an existing incomplete version you must set this environment variable. Refer to +[Version Fingerprinting](#version-fingerprinting) for usage details. -- `HCP_PACKER_REGISTRY` - When set, Packer does not push image metadata to HCP Packer from an otherwise configured template. Allowed values are [0|OFF]. +- `HCP_PACKER_REGISTRY` - When set, Packer does not push artifact metadata to HCP Packer from an otherwise +configured template. Allowed values are [0|OFF]. -- `HCP_ORGANIZATION_ID` - The ID of the HCP organization linked to your service principal. This is environment variable is not required and available for the sole purpose of keeping parity with the HCP SDK authentication options. Its use may change in a future release. +- `HCP_ORGANIZATION_ID` - The ID of the HCP organization linked to your service principal. This is environment +variable is not required and available for the sole purpose of keeping parity with the HCP SDK authentication options. +Its use may change in a future release. -- `HCP_PROJECT_ID` - The ID of the HCP project to use. This is useful if your service principal has access to multiple projects, as by default Packer will pick the one created first as target. +- `HCP_PROJECT_ID` - The ID of the HCP project to use. This is useful if your service principal has access to multiple +projects, as by default Packer will pick the one created first as target. --> **Note**: The HCP_PROJECT_ID environment variable must be set if you're authenticating with a project-level service principal, otherwise Packer will attempt to get the list of projects for an organization and error due to a lack of permissions for a project-level service principal. This is supported starting with Packer 1.9.3; older versions of Packer do not support using project-level service principals. +-> **Note**: The HCP_PROJECT_ID environment variable must be set if you're authenticating with a project-level service +principal, otherwise Packer will attempt to get the list of projects for an organization and error due to a lack of +permissions for a project-level service principal. This is supported starting with Packer 1.9.3; older versions of +Packer do not support using project-level service principals. ### HCP Packer Registry Block -The only metadata that Packer can infer from a template with the basic configuration are the build name and build fingerprint. For HCL2 templates, we recommend adding the `hcp_packer_registry` block to your template so that you can customize the metadata that Packer sends to the registry. +The only metadata that Packer can infer from a template with the basic configuration are the build name and build fingerprint. +For HCL2 templates, we recommend adding the `hcp_packer_registry` block to your template so that you can customize +the metadata that Packer sends to the registry. The `hcp_packer_registry` block is only available for HCL2 Packer templates. There is no [`PACKER_CONFIG`](/packer/docs/configure#packer-s-config-file) equivalent for JSON. -Refer to [`hcp_packer_registry`](/packer/docs/templates/hcl_templates/blocks/build/hcp_packer_registry) for a full list of configuration arguments. Refer to [Custom Configuration](/hcp/docs/packer/store-image-metadata/packer-template-configuration#custom-configuration) in the HCP Packer documentation for information and examples about how to customize image metadata. +Refer to [`hcp_packer_registry`](/packer/docs/templates/hcl_templates/blocks/build/hcp_packer_registry) for a full list +of configuration arguments. Refer to [Custom Configuration](/hcp/docs/packer/store-image-metadata/packer-template-configuration#custom-configuration) +in the HCP Packer documentation for information and examples about how to customize artifact metadata. -### Iteration Fingerprinting +### Version Fingerprinting -Packer uses a unique fingerprint for tracking the completion of builds associated to an iteration. By default a fingerprint is automatically generated by Packer during each invocation of `packer build`, unless a fingerprint is manually provided via the `HCP_PACKER_BUILD_FINGERPRINT` environment variable. +Packer uses a unique fingerprint for tracking the completion of builds associated to a version. By default a fingerprint +is automatically generated by Packer during each invocation of `packer build`, unless a fingerprint is manually provided +via the `HCP_PACKER_BUILD_FINGERPRINT` environment variable. -In versions before 1.9.0, this fingerprint was computed from the Git SHA of the current HEAD in which your template is stored. If you were running builds using a non Git managed template, you had to set the `HCP_PACKER_BUILD_FINGERPRINT` environment variable prior to invoking `packer build`. -Starting with Packer 1.9.0, fingerprint generation does not rely on Git at all, and instead Packer now generates a Unique Lexicographically sortable Identifier (ULID) as the fingerprint for every `packer build` invocation. +In versions before 1.9.0, this fingerprint was computed from the Git SHA of the current HEAD in which your template is +stored. If you were running builds using a non Git managed template, you had to set the `HCP_PACKER_BUILD_FINGERPRINT` +environment variable prior to invoking `packer build`. +Starting with Packer 1.9.0, fingerprint generation does not rely on Git at all, and instead Packer now generates +a Unique Lexicographically sortable Identifier (ULID) as the fingerprint for every `packer build` invocation. -#### Fingerprints and Incomplete Iterations +#### Fingerprints and Incomplete Versions -When you build a template with Packer, there's always a chance that it does not succeed because of a network issue, a provisioning failure, or some upstream error. When that happens, Packer will output the generated fingerprint associated with the incomplete iteration so that you can resume building that iteration using the `HCP_PACKER_BUILD_FINGERPRINT` environment variable; an iteration can be resumed until it is marked as complete. This environment variable is necessary for resuming an incomplete iteration, otherwise Packer will create a new iteration for the build. +When you build a template with Packer, there's always a chance that it does not succeed because of a network issue, +a provisioning failure, or some upstream error. When that happens, Packer will output the generated fingerprint +associated with the incomplete version so that you can resume building that version using the `HCP_PACKER_BUILD_FINGERPRINT` +environment variable; a version can be resumed until it is marked as complete. This environment variable is necessary +for resuming an incomplete version, otherwise Packer will create a new version for the build. There are two alternatives for when and how to set your own fingerprint: -* You can set it prior to invoking `packer build` for the first time on this template. This will require the fingerprint to be unique, otherwise Packer will attempt to continue the iteration with the same fingerprint. -* You can invoke `packer build` on the template, and if it fails, you can then get the fingerprint from the output of the command and set it for subsequent runs, Packer will then continue building this iteration. +* You can set it prior to invoking `packer build` for the first time on this template. This will require the +fingerprint to be unique, otherwise Packer will attempt to continue the version with the same fingerprint. +* You can invoke `packer build` on the template, and if it fails, you can then get the fingerprint from the output of +the command and set it for subsequent runs, Packer will then continue building this version. -The first alternative is recommended for CI environments, as you can use environment variables from the CI to generate a unique, deterministic, fingerprint, and then re-use this in case the step fails for any reason. This will let you continue building the same iteration, rather than creating a new one on each invocation. +The first alternative is recommended for CI environments, as you can use environment variables from the CI to generate +a unique, deterministic, fingerprint, and then re-use this in case the step fails for any reason. This will let you +continue building the same version, rather than creating a new one on each invocation. -The second alternative is good for local builds, as you can interact with the build environment directly, and therefore can decide if you want to continue building an iteration by setting the fingerprint provided by Packer in case of failure. +The second alternative is good for local builds, as you can interact with the build environment directly, and therefore +can decide if you want to continue building a version by setting the fingerprint provided by Packer in case of failure. -Please note that in all cases, an iteration can only be continued if it has not completed yet. Once an iteration is complete, it cannot be modified, and you will have to create a new one. +Please note that in all cases, a version can only be continued if it has not completed yet. Once a version is +complete, it cannot be modified, and you will have to create a new one. diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index 5fd386e1dc7..b4ab7cf0cd6 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -12,6 +12,8 @@ To install Packer and learn the standard Packer workflow, try the [Get Started t ## HCP Packer -The HCP Packer registry stores metadata about your images. You can use the registry to track information about golden images from your Packer builds, clearly designate which images are appropriate for test and production environments, and query for the right images to use in both Packer and Terraform configurations. +The HCP Packer registry stores metadata about your artifacts. You can use the registry to track information about +artifacts from your Packer builds, clearly designate which artifacts are appropriate for test and production +environments, and query for the right artifacts to use in both Packer and Terraform configurations. To get started, visit the [HCP Packer documentation](/hcp/docs/packer) or try the [Get Started with HCP Packer tutorials](/packer/tutorials/hcp-get-started). diff --git a/website/content/partials/datasource/hcp-packer-artifact/Config-not-required.mdx b/website/content/partials/datasource/hcp-packer-artifact/Config-not-required.mdx new file mode 100644 index 00000000000..f8b0957fe28 --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-artifact/Config-not-required.mdx @@ -0,0 +1,6 @@ + + +- `component_type` (string) - The specific Packer builder used to create the artifact. + For example, "amazon-ebs.example" + + diff --git a/website/content/partials/datasource/hcp-packer-artifact/Config-required.mdx b/website/content/partials/datasource/hcp-packer-artifact/Config-required.mdx new file mode 100644 index 00000000000..2053ddc1d66 --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-artifact/Config-required.mdx @@ -0,0 +1,22 @@ + + +- `bucket_name` (string) - The name of the bucket your artifact is in. + +- `channel_name` (string) - The name of the channel to use when retrieving your artifact. + Either `channel_name` or `version_fingerprint` MUST be set. + If using several artifacts from a single version, you may prefer sourcing a version first, + and referencing it for subsequent uses, as every `hcp_packer_artifact` with the channel set will generate a + potentially billable HCP Packer request, but if several `hcp_packer_artifact`s use a shared `hcp_packer_version` + that will only generate one potentially billable request. + +- `version_fingerprint` (string) - The fingerprint of the version to use when retrieving your artifact. + Either this or `channel_name` MUST be set. + Mutually exclusive with `channel_name` + +- `platform` (string) - The name of the platform that your artifact is for. + For example, "aws", "azure", or "gce". + +- `region` (string) - The name of the region your artifact is in. + For example "us-east-1". + + diff --git a/website/content/partials/datasource/hcp-packer-artifact/DatasourceOutput.mdx b/website/content/partials/datasource/hcp-packer-artifact/DatasourceOutput.mdx new file mode 100644 index 00000000000..dcd90f59092 --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-artifact/DatasourceOutput.mdx @@ -0,0 +1,31 @@ + + +- `platform` (string) - The name of the platform that the artifact exists in. + For example, "aws", "azure", or "gce". + +- `component_type` (string) - The specific Packer builder or post-processor used to create the artifact. + +- `created_at` (string) - The date and time at which the artifact was created. + +- `build_id` (string) - The ID of the build that created the artifact. This is a ULID, which is a + unique identifier similar to a UUID. It is created by the HCP Packer + Registry when a build is first created, and is unique to this build. + +- `version_id` (string) - The version ID. This is a ULID, which is a unique identifier similar + to a UUID. It is created by the HCP Packer Registry when a version is + first created, and is unique to this version. + +- `channel_id` (string) - The ID of the channel used to query the version. This value will be empty if the `version_fingerprint` was used + directly instead of a channel. + +- `packer_run_uuid` (string) - The UUID associated with the Packer run that created this artifact. + +- `external_identifier` (string) - Identifier or URL of the remote artifact as given by a build. + For example, ami-12345. + +- `region` (string) - The region as given by `packer build`. eg. "ap-east-1". + For locally managed clouds, this may map instead to a cluster, server or datastore. + +- `labels` (map[string]string) - The key:value metadata labels associated with this build. + + diff --git a/website/content/partials/datasource/hcp-packer-image/Config-required.mdx b/website/content/partials/datasource/hcp-packer-image/Config-required.mdx index cca6bec0984..95abfd1fe85 100644 --- a/website/content/partials/datasource/hcp-packer-image/Config-required.mdx +++ b/website/content/partials/datasource/hcp-packer-image/Config-required.mdx @@ -7,9 +7,9 @@ Mutually exclusive with `iteration_id`. If using several images from a single iteration, you may prefer sourcing an iteration first, and referencing it for subsequent uses, - as every `hcp_packer_image` with the channel set will generate a + as every `hcp-packer-image` with the channel set will generate a potentially billable HCP Packer request, but if several - `hcp_packer_image`s use a shared `hcp_packer_iteration` that will + `hcp-packer-image`s use a shared `hcp-packer-iteration` that will only generate one potentially billable request. - `iteration_id` (string) - The ID of the iteration to use when retrieving your image diff --git a/website/content/partials/datasource/hcp-packer-version/Config-required.mdx b/website/content/partials/datasource/hcp-packer-version/Config-required.mdx new file mode 100644 index 00000000000..f97aced7e3c --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-version/Config-required.mdx @@ -0,0 +1,7 @@ + + +- `bucket_name` (string) - The bucket name in the HCP Packer Registry. + +- `channel_name` (string) - The channel name in the given bucket to use for retrieving the version. + + diff --git a/website/content/partials/datasource/hcp-packer-version/DatasourceOutput.mdx b/website/content/partials/datasource/hcp-packer-version/DatasourceOutput.mdx new file mode 100644 index 00000000000..3a14240ce03 --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-version/DatasourceOutput.mdx @@ -0,0 +1,25 @@ + + +- `author_id` (string) - Name of the author who created this version. + +- `bucket_name` (string) - The name of the bucket that this version is associated with. + +- `status` (string) - Current state of the version. + +- `created_at` (string) - The date the version was created. + +- `fingerprint` (string) - The fingerprint of the version; this is a unique identifier set by the Packer build + that created this version. + +- `id` (string) - The version ID. This is a ULID, which is a unique identifier similar + to a UUID. It is created by the HCP Packer Registry when a version is + first created, and is unique to this version. + +- `name` (string) - The version name is created by the HCP Packer Registry once a version is + "complete". Incomplete or failed versions currently default to having a name "v0". + +- `updated_at` (string) - The date when this version was last updated. + +- `channel_id` (string) - The ID of the channel used to query this version. + + diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 7c7e110d518..807fb16f968 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -729,6 +729,14 @@ "title": "Overview", "path": "datasources/hcp" }, + { + "title": "Version", + "path": "datasources/hcp/hcp-packer-version" + }, + { + "title": "Artifact", + "path": "datasources/hcp/hcp-packer-artifact" + }, { "title": "Iteration", "path": "datasources/hcp/hcp-packer-iteration"