From fabfc8f0150b2834d458165083839bc1612063ea Mon Sep 17 00:00:00 2001 From: Lucas Bajolet Date: Wed, 6 Jul 2022 15:32:36 -0400 Subject: [PATCH] hcp-packer-image: add support for channel In addition to the current way of specifying an image based on an iteration on HCP Packer, which requires first declaring an iteration, and then referencing it from the image to build, we add the capacity of specifying a channel. This alternative will get the iteration linked to the channel, and is essentially a more convenient way to get an image's metadata from HCP Packer. This commit is essentially a backport from the Terraform HCP provider, for consistency between the two tools. --- datasource/hcp-packer-image/data.go | 62 ++++++++++++++++--- datasource/hcp-packer-image/data.hcl2spec.go | 2 + .../hcp-packer-image/Config-required.mdx | 12 ++++ 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/datasource/hcp-packer-image/data.go b/datasource/hcp-packer-image/data.go index 75eb87ddf..3fe393548 100644 --- a/datasource/hcp-packer-image/data.go +++ b/datasource/hcp-packer-image/data.go @@ -4,6 +4,7 @@ package hcp_packer_image import ( "context" + "errors" "fmt" "log" "time" @@ -11,10 +12,12 @@ 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" "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" + "github.com/hashicorp/packer/internal/registry" packerregistry "github.com/hashicorp/packer/internal/registry" ) @@ -26,7 +29,19 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // The name of the bucket your image is in. Bucket string `mapstructure:"bucket_name" required:"true"` + // The name of the channel to use when retrieving your image. + // Either this or `iteration_id` MUST be set. + // 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 + // potentially billable HCP Packer request, but if several + // `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 name of the iteration Id to use when retrieving your image + // Either this or `channel` MUST be set. + // Mutually exclusive with `channel` IterationID string `mapstructure:"iteration_id" required:"true"` // The name of the cloud provider that your image is for. For example, // "aws" or "gce". @@ -53,12 +68,19 @@ func (d *Datasource) Configure(raws ...interface{}) error { if d.config.Bucket == "" { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("The `bucket_name` must be specified")) } - if d.config.IterationID == "" { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("The `iteration_id`"+ - " must be specified. If you do not know your iteration_id, you "+ - "can retrieve it using the bucket name and desired channel using"+ - " the hcp-packer-iteration data source.")) + + // Ensure either channel or iteration_id are set, and not both at the same time + if d.config.Channel == "" && + d.config.IterationID == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New( + "The `iteration_id` or `channel` must be specified.")) + } + if d.config.Channel != "" && + d.config.IterationID != "" { + errs = packersdk.MultiErrorAppend(errs, errors.New( + "`iteration_id` and `channel` cannot both be specified.")) } + if d.config.Region == "" { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`region` is "+ "currently a required field.")) @@ -109,6 +131,31 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec { return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec() } +func (d *Datasource) getIteration( + ctx context.Context, + cli *registry.Client, +) (*models.HashicorpCloudPackerIteration, error) { + if d.config.IterationID != "" { + iter, err := cli.GetIteration(ctx, d.config.Bucket, packerregistry.GetIteration_byID(d.config.IterationID)) + if err != nil { + return nil, fmt.Errorf( + "error retrieving image iteration from HCP Packer registry: %s", + err) + } + + return iter, nil + } + + iter, err := cli.GetIterationFromChannel(ctx, d.config.Bucket, d.config.Channel) + if err != nil { + return nil, fmt.Errorf( + "error retrieving iteration from HCP Packer registry: %s", + err) + } + + return iter, nil +} + func (d *Datasource) Execute() (cty.Value, error) { ctx := context.TODO() @@ -121,10 +168,9 @@ func (d *Datasource) Execute() (cty.Value, error) { log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, iteration_id=%s]", d.config.Bucket, cli.ProjectID, cli.OrganizationID, d.config.IterationID) - iteration, err := cli.GetIteration(ctx, d.config.Bucket, packerregistry.GetIteration_byID(d.config.IterationID)) + iteration, err := d.getIteration(ctx, cli) if err != nil { - return cty.NullVal(cty.EmptyObject), fmt.Errorf("error retrieving "+ - "image iteration from HCP Packer registry: %s", err.Error()) + return cty.NullVal(cty.EmptyObject), err } revokeAt := time.Time(iteration.RevokeAt) diff --git a/datasource/hcp-packer-image/data.hcl2spec.go b/datasource/hcp-packer-image/data.hcl2spec.go index 99f3239b3..3e7f6fa89 100644 --- a/datasource/hcp-packer-image/data.hcl2spec.go +++ b/datasource/hcp-packer-image/data.hcl2spec.go @@ -19,6 +19,7 @@ type FlatConfig struct { 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"` Bucket *string `mapstructure:"bucket_name" required:"true" cty:"bucket_name" hcl:"bucket_name"` + Channel *string `mapstructure:"channel" required:"true" cty:"channel" hcl:"channel"` IterationID *string `mapstructure:"iteration_id" required:"true" cty:"iteration_id" hcl:"iteration_id"` CloudProvider *string `mapstructure:"cloud_provider" required:"true" cty:"cloud_provider" hcl:"cloud_provider"` Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` @@ -45,6 +46,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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": &hcldec.AttrSpec{Name: "channel", Type: cty.String, Required: false}, "iteration_id": &hcldec.AttrSpec{Name: "iteration_id", Type: cty.String, Required: false}, "cloud_provider": &hcldec.AttrSpec{Name: "cloud_provider", Type: cty.String, Required: false}, "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, 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 64c3343c2..a1395b0f6 100644 --- a/website/content/partials/datasource/hcp-packer-image/Config-required.mdx +++ b/website/content/partials/datasource/hcp-packer-image/Config-required.mdx @@ -2,7 +2,19 @@ - `bucket_name` (string) - The name of the bucket your image is in. +- `channel` (string) - The name of the channel to use when retrieving your image. + Either this or `iteration_id` MUST be set. + 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 + potentially billable HCP Packer request, but if several + `hcp_packer_image`s use a shared `hcp_packer_iteration` that will + only generate one potentially billable request. + - `iteration_id` (string) - The name of the iteration Id to use when retrieving your image + Either this or `channel` MUST be set. + Mutually exclusive with `channel` - `cloud_provider` (string) - The name of the cloud provider that your image is for. For example, "aws" or "gce".