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 }