github.com/hashicorp/packer@v1.14.3/datasource/hcp-packer-image/data.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  //go:generate packer-sdc struct-markdown
     5  //go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config
     6  package hcp_packer_image
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"log"
    13  	"time"
    14  
    15  	"github.com/zclconf/go-cty/cty"
    16  
    17  	"github.com/hashicorp/hcl/v2/hcldec"
    18  	hcpPackerDeprecatedModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
    19  	"github.com/hashicorp/packer-plugin-sdk/common"
    20  	"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
    21  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    22  	"github.com/hashicorp/packer-plugin-sdk/template/config"
    23  	hcpapi "github.com/hashicorp/packer/internal/hcp/api"
    24  )
    25  
    26  type Datasource struct {
    27  	config Config
    28  }
    29  
    30  type Config struct {
    31  	common.PackerConfig `mapstructure:",squash"`
    32  	// The name of the bucket your image is in.
    33  	Bucket string `mapstructure:"bucket_name" required:"true"`
    34  	// The name of the channel to use when retrieving your image.
    35  	// Either this or `iteration_id` MUST be set.
    36  	// Mutually exclusive with `iteration_id`.
    37  	// If using several images from a single iteration, you may prefer
    38  	// sourcing an iteration first, and referencing it for subsequent uses,
    39  	// as every `hcp-packer-image` with the channel set will generate a
    40  	// potentially billable HCP Packer request, but if several
    41  	// `hcp-packer-image`s use a shared `hcp-packer-iteration` that will
    42  	// only generate one potentially billable request.
    43  	Channel string `mapstructure:"channel" required:"true"`
    44  	// The ID of the iteration to use when retrieving your image
    45  	// Either this or `channel` MUST be set.
    46  	// Mutually exclusive with `channel`
    47  	IterationID string `mapstructure:"iteration_id" required:"true"`
    48  	// The name of the cloud provider that your image is for. For example,
    49  	// "aws" or "gce".
    50  	CloudProvider string `mapstructure:"cloud_provider" required:"true"`
    51  	// The name of the cloud region your image is in. For example "us-east-1".
    52  	Region string `mapstructure:"region" required:"true"`
    53  	// The specific Packer builder used to create the image.
    54  	// For example, "amazon-ebs.example"
    55  	ComponentType string `mapstructure:"component_type" required:"false"`
    56  	// TODO: Version          string `mapstructure:"version"`
    57  	// TODO: Fingerprint          string `mapstructure:"fingerprint"`
    58  	// TODO: Label          string `mapstructure:"label"`
    59  }
    60  
    61  func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
    62  	return d.config.FlatMapstructure().HCL2Spec()
    63  }
    64  
    65  func (d *Datasource) Configure(raws ...interface{}) error {
    66  	err := config.Decode(&d.config, nil, raws...)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	var errs *packersdk.MultiError
    72  
    73  	if d.config.Bucket == "" {
    74  		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("The `bucket_name` must be specified"))
    75  	}
    76  
    77  	// Ensure either channel or iteration_id are set, and not both at the same time
    78  	if d.config.Channel == "" &&
    79  		d.config.IterationID == "" {
    80  		errs = packersdk.MultiErrorAppend(errs, errors.New(
    81  			"The `iteration_id` or `channel` must be specified."))
    82  	}
    83  	if d.config.Channel != "" &&
    84  		d.config.IterationID != "" {
    85  		errs = packersdk.MultiErrorAppend(errs, errors.New(
    86  			"`iteration_id` and `channel` cannot both be specified."))
    87  	}
    88  
    89  	if d.config.Region == "" {
    90  		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`region` is "+
    91  			"currently a required field."))
    92  	}
    93  	if d.config.CloudProvider == "" {
    94  		errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`cloud_provider` is "+
    95  			"currently a required field."))
    96  	}
    97  
    98  	if errs != nil && len(errs.Errors) > 0 {
    99  		return errs
   100  	}
   101  	return nil
   102  }
   103  
   104  // DatasourceOutput Information from []*hcpPackerDeprecatedModels.HashicorpCloudPackerImage with some information
   105  // from the parent []*hcpPackerDeprecatedModels.HashicorpCloudPackerBuild included where it seemed
   106  // like it might be relevant. Need to copy so we can generate
   107  type DatasourceOutput struct {
   108  	// The name of the cloud provider that the image exists in. For example,
   109  	// "aws", "azure", or "gce".
   110  	CloudProvider string `mapstructure:"cloud_provider"`
   111  	// The specific Packer builder or post-processor used to create the image.
   112  	ComponentType string `mapstructure:"component_type"`
   113  	// The date and time at which the image was created.
   114  	CreatedAt string `mapstructure:"created_at"`
   115  	// The ID of the build that created the image. This is a ULID, which is a
   116  	// unique identifier similar to a UUID. It is created by the HCP Packer
   117  	// Registry when an build is first created, and is unique to this build.
   118  	BuildID string `mapstructure:"build_id"`
   119  	// The iteration ID. This is a ULID, which is a unique identifier similar
   120  	// to a UUID. It is created by the HCP Packer Registry when an iteration is
   121  	// first created, and is unique to this iteration.
   122  	IterationID string `mapstructure:"iteration_id"`
   123  	// The ID of the channel used to query the image iteration. This value will be empty if the `iteration_id` was used
   124  	// directly instead of a channel.
   125  	ChannelID string `mapstructure:"channel_id"`
   126  	// The UUID associated with the Packer run that created this image.
   127  	PackerRunUUID string `mapstructure:"packer_run_uuid"`
   128  	// ID or URL of the remote cloud image as given by a build.
   129  	ID string `mapstructure:"id"`
   130  	// The cloud region as given by `packer build`. eg. "ap-east-1".
   131  	// For locally managed clouds, this may map instead to a cluster, server
   132  	// or datastore.
   133  	Region string `mapstructure:"region"`
   134  	// The key:value metadata labels associated with this build.
   135  	Labels map[string]string `mapstructure:"labels"`
   136  }
   137  
   138  func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
   139  	return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
   140  }
   141  
   142  func (d *Datasource) Execute() (cty.Value, error) {
   143  	log.Printf("[WARN] Deprecation: `hcp-packer-image` datasource has been deprecated. " +
   144  		"Please use `hcp-packer-artifact` datasource instead.")
   145  	ctx := context.TODO()
   146  
   147  	cli, err := hcpapi.NewDeprecatedClient()
   148  	if err != nil {
   149  		return cty.NullVal(cty.EmptyObject), err
   150  	}
   151  
   152  	var iteration *hcpPackerDeprecatedModels.HashicorpCloudPackerIteration
   153  	var channelID string
   154  	if d.config.IterationID != "" {
   155  		log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, iteration_id=%s]",
   156  			d.config.Bucket, cli.ProjectID, cli.OrganizationID, d.config.IterationID)
   157  
   158  		iter, err := cli.GetIteration(ctx, d.config.Bucket, hcpapi.GetIteration_byID(d.config.IterationID))
   159  		if err != nil {
   160  			return cty.NullVal(cty.EmptyObject), fmt.Errorf(
   161  				"error retrieving image iteration from HCP Packer registry: %s",
   162  				err)
   163  		}
   164  		iteration = iter
   165  	} else {
   166  		log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, channel=%s]",
   167  			d.config.Bucket, cli.ProjectID, cli.OrganizationID, d.config.Channel)
   168  
   169  		channel, err := cli.GetChannel(ctx, d.config.Bucket, d.config.Channel)
   170  		if err != nil {
   171  			return cty.NullVal(cty.EmptyObject), fmt.Errorf("error retrieving "+
   172  				"channel from HCP Packer registry: %s", err.Error())
   173  		}
   174  
   175  		if channel.Iteration == nil {
   176  			return cty.NullVal(cty.EmptyObject), fmt.Errorf("there is no iteration associated with the channel %s",
   177  				d.config.Channel)
   178  		}
   179  		channelID = channel.ID
   180  		iteration = channel.Iteration
   181  	}
   182  
   183  	revokeAt := time.Time(iteration.RevokeAt)
   184  	if !revokeAt.IsZero() && revokeAt.Before(time.Now().UTC()) {
   185  		// If RevokeAt is not a zero date and is before NOW, it means this iteration is revoked and should not be used
   186  		// to build new images.
   187  		return cty.NullVal(cty.EmptyObject), fmt.Errorf("the iteration %s is revoked and can not be used on Packer builds",
   188  			iteration.ID)
   189  	}
   190  
   191  	var output DatasourceOutput
   192  
   193  	cloudAndRegions := map[string][]string{}
   194  	for _, build := range iteration.Builds {
   195  		if build.CloudProvider != d.config.CloudProvider {
   196  			continue
   197  		}
   198  		for _, image := range build.Images {
   199  			cloudAndRegions[build.CloudProvider] = append(cloudAndRegions[build.CloudProvider], image.Region)
   200  			if image.Region == d.config.Region && filterBuildByComponentType(build, d.config.ComponentType) {
   201  				// This is the desired image.
   202  				output = DatasourceOutput{
   203  					CloudProvider: build.CloudProvider,
   204  					ComponentType: build.ComponentType,
   205  					CreatedAt:     image.CreatedAt.String(),
   206  					BuildID:       build.ID,
   207  					IterationID:   build.IterationID,
   208  					ChannelID:     channelID,
   209  					PackerRunUUID: build.PackerRunUUID,
   210  					ID:            image.ImageID,
   211  					Region:        image.Region,
   212  					Labels:        build.Labels,
   213  				}
   214  				return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
   215  			}
   216  		}
   217  	}
   218  
   219  	return cty.NullVal(cty.EmptyObject), fmt.Errorf("could not find a build result matching "+
   220  		"[region=%q, cloud_provider=%q, component_type=%q]. Available: %v ",
   221  		d.config.Region, d.config.CloudProvider, d.config.ComponentType, cloudAndRegions)
   222  }
   223  
   224  func filterBuildByComponentType(build *hcpPackerDeprecatedModels.HashicorpCloudPackerBuild, componentType string) bool {
   225  	// optional field is not specified, passthrough
   226  	if componentType == "" {
   227  		return true
   228  	}
   229  	// if specified, only the matched image metadata is returned by this effect
   230  	return build.ComponentType == componentType
   231  }