From d73699c6ff5de9f2c12e24a96e85d0b709a03ee2 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 | 81 ++++++++++++++++--- datasource/hcp-packer-image/data.hcl2spec.go | 4 +- .../hcp-packer-image/Config-not-required.mdx | 19 +++++ .../hcp-packer-image/Config-required.mdx | 2 - 4 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 website/content/partials/datasource/hcp-packer-image/Config-not-required.mdx diff --git a/datasource/hcp-packer-image/data.go b/datasource/hcp-packer-image/data.go index 75eb87ddf13..421440a9278 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,8 +29,22 @@ 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. + // + // If using several images from a single iteration, you may prefer + // sourcing an iteration first, and referencing it for subsequent uses, + // as getting the iteration induces a potentially billable request. + // + // Either this or `iteration_id` MUST be set. + // + // Mutually exclusive with `iteration_id` + Channel string `mapstructure:"channel" required:"false"` // The name of the iteration Id to use when retrieving your image - IterationID string `mapstructure:"iteration_id" required:"true"` + // + // Either this or `channel` MUST be set. + // + // Mutually exclusive with `channel` + IterationID string `mapstructure:"iteration_id" required:"false"` // The name of the cloud provider that your image is for. For example, // "aws" or "gce". CloudProvider string `mapstructure:"cloud_provider" required:"true"` @@ -53,12 +70,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 +133,46 @@ 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 == "" && + d.config.Channel == "" { + return nil, fmt.Errorf( + "`channel` and `iteration_id` cannot be unset at the same time for one image") + } + + if d.config.IterationID != "" && + d.config.Channel != "" { + return nil, fmt.Errorf( + "`channel` and `iteration_id` cannot be set at the same time for one image") + } + + var iter *models.HashicorpCloudPackerIteration + var err 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 + } + + ch, 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 ch, nil +} + func (d *Datasource) Execute() (cty.Value, error) { ctx := context.TODO() @@ -121,10 +185,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 99f3239b393..b59147c738b 100644 --- a/datasource/hcp-packer-image/data.hcl2spec.go +++ b/datasource/hcp-packer-image/data.hcl2spec.go @@ -19,7 +19,8 @@ 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"` - IterationID *string `mapstructure:"iteration_id" required:"true" cty:"iteration_id" hcl:"iteration_id"` + Channel *string `mapstructure:"channel" required:"false" cty:"channel" hcl:"channel"` + IterationID *string `mapstructure:"iteration_id" required:"false" 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-not-required.mdx b/website/content/partials/datasource/hcp-packer-image/Config-not-required.mdx new file mode 100644 index 00000000000..0f5ce048022 --- /dev/null +++ b/website/content/partials/datasource/hcp-packer-image/Config-not-required.mdx @@ -0,0 +1,19 @@ + + +- `channel` (string) - The name of the channel to use when retrieving your image. + + If using several images from a single iteration, you may prefer + sourcing an iteration first, and referencing it for subsequent uses, + as getting the iteration induces a potentially billable request. + + Either this or `iteration_id` MUST be set. + + Mutually exclusive with `iteration_id` + +- `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` + + 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 64c3343c259..f673db0fb05 100644 --- a/website/content/partials/datasource/hcp-packer-image/Config-required.mdx +++ b/website/content/partials/datasource/hcp-packer-image/Config-required.mdx @@ -2,8 +2,6 @@ - `bucket_name` (string) - The name of the bucket your image is in. -- `iteration_id` (string) - The name of the iteration Id to use when retrieving your image - - `cloud_provider` (string) - The name of the cloud provider that your image is for. For example, "aws" or "gce".