github.com/hashicorp/packer@v1.14.3/internal/hcp/registry/hcl.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package registry 5 6 import ( 7 "context" 8 "fmt" 9 "log" 10 11 "github.com/hashicorp/hcl/v2" 12 hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" 13 sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer" 14 "github.com/hashicorp/packer/hcl2template" 15 "github.com/hashicorp/packer/packer" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 // HCLRegistry is a HCP handler made for handling HCL configurations 20 type HCLRegistry struct { 21 configuration *hcl2template.PackerConfig 22 bucket *Bucket 23 ui sdkpacker.Ui 24 metadata *MetadataStore 25 buildNames map[string]struct{} 26 } 27 28 const ( 29 // Known HCP Packer Datasource, whose id is the SourceImageId for some build. 30 hcpImageDatasourceType string = "hcp-packer-image" 31 hcpArtifactDatasourceType string = "hcp-packer-artifact" 32 33 hcpIterationDatasourceType string = "hcp-packer-iteration" 34 hcpVersionDatasourceType string = "hcp-packer-version" 35 36 buildLabel string = "build" 37 ) 38 39 // PopulateVersion creates the metadata in HCP Packer Registry for a build 40 func (h *HCLRegistry) PopulateVersion(ctx context.Context) error { 41 err := h.bucket.Initialize(ctx, hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2) 42 if err != nil { 43 return err 44 } 45 46 err = h.bucket.populateVersion(ctx) 47 if err != nil { 48 return err 49 } 50 51 versionID := h.bucket.Version.ID 52 versionFingerprint := h.bucket.Version.Fingerprint 53 54 // FIXME: Remove 55 h.configuration.HCPVars["iterationID"] = cty.StringVal(versionID) 56 h.configuration.HCPVars["versionFingerprint"] = cty.StringVal(versionFingerprint) 57 58 sha, err := getGitSHA(h.configuration.Basedir) 59 if err != nil { 60 log.Printf("failed to get GIT SHA from environment, won't set as build labels") 61 } else { 62 h.bucket.Version.AddSHAToBuildLabels(sha) 63 } 64 65 return nil 66 } 67 68 // StartBuild is invoked when one build for the configuration is starting to be processed 69 func (h *HCLRegistry) StartBuild(ctx context.Context, build *packer.CoreBuild) error { 70 return h.bucket.startBuild(ctx, h.HCPBuildName(build)) 71 } 72 73 // CompleteBuild is invoked when one build for the configuration has finished 74 func (h *HCLRegistry) CompleteBuild( 75 ctx context.Context, 76 build *packer.CoreBuild, 77 artifacts []sdkpacker.Artifact, 78 buildErr error, 79 ) ([]sdkpacker.Artifact, error) { 80 buildName := h.HCPBuildName(build) 81 buildMetadata, envMetadata := build.GetMetadata(), h.metadata 82 err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata, envMetadata) 83 if err != nil { 84 return nil, err 85 } 86 return h.bucket.completeBuild(ctx, buildName, artifacts, buildErr) 87 } 88 89 // VersionStatusSummary prints a status report in the UI if the version is not yet done 90 func (h *HCLRegistry) VersionStatusSummary() { 91 h.bucket.Version.statusSummary(h.ui) 92 } 93 94 func NewHCLRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLRegistry, hcl.Diagnostics) { 95 var diags hcl.Diagnostics 96 if len(config.Builds) > 1 { 97 diags = append(diags, &hcl.Diagnostic{ 98 Severity: hcl.DiagError, 99 Summary: "Multiple " + buildLabel + " blocks", 100 Detail: fmt.Sprintf("For HCP Packer Registry enabled builds, only one " + buildLabel + 101 " block can be defined. Please remove any additional " + buildLabel + 102 " block(s). If this " + buildLabel + " is not meant for the HCP Packer registry please " + 103 "clear any HCP_PACKER_* environment variables."), 104 }) 105 106 return nil, diags 107 } 108 109 registryConfig, rcDiags := config.GetHCPPackerRegistryBlock() 110 diags = diags.Extend(rcDiags) 111 if diags.HasErrors() { 112 return nil, diags 113 } 114 115 withHCLBucketConfiguration := func(bucket *Bucket) hcl.Diagnostics { 116 bucket.ReadFromHCPPackerRegistryBlock(registryConfig) 117 return nil 118 } 119 120 // we must use the old strategy when there is only a single build block because 121 // we used to rely on the parent build block for setting some default data 122 if len(config.Builds) == 1 && config.HCPPackerRegistry == nil { 123 withHCLBucketConfiguration = func(bucket *Bucket) hcl.Diagnostics { 124 bb := config.Builds[0] 125 bucket.ReadFromHCLBuildBlock(bb) 126 // If at this point the bucket.Name is still empty, 127 // last try is to use the build.Name if present 128 if bucket.Name == "" && bb.Name != "" { 129 bucket.Name = bb.Name 130 } 131 132 // If the description is empty, use the one from the build block 133 if bucket.Description == "" && bb.Description != "" { 134 bucket.Description = bb.Description 135 } 136 return nil 137 } 138 } 139 140 // Capture Datasource configuration data 141 vals, dsDiags := config.Datasources.Values() 142 if dsDiags != nil { 143 diags = append(diags, dsDiags...) 144 } 145 146 bucket, bucketDiags := createConfiguredBucket( 147 config.Basedir, 148 withPackerEnvConfiguration, 149 withHCLBucketConfiguration, 150 withDeprecatedDatasourceConfiguration(vals, ui), 151 withDatasourceConfiguration(vals), 152 ) 153 if bucketDiags != nil { 154 diags = append(diags, bucketDiags...) 155 } 156 157 if diags.HasErrors() { 158 return nil, diags 159 } 160 161 registry := &HCLRegistry{ 162 configuration: config, 163 bucket: bucket, 164 ui: ui, 165 metadata: &MetadataStore{}, 166 buildNames: map[string]struct{}{}, 167 } 168 169 ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Version.Fingerprint)) 170 171 return registry, diags.Extend(registry.registerAllComponents()) 172 } 173 174 func (h *HCLRegistry) registerAllComponents() hcl.Diagnostics { 175 var diags hcl.Diagnostics 176 177 conflictSources := map[string]struct{}{} 178 179 // we currently support only one build block but it will change in the near future 180 for _, build := range h.configuration.Builds { 181 for _, source := range build.Sources { 182 // If we encounter the same source twice, we'll defer 183 // its addition to later, using both the build name 184 // and the source type as the name used for HCP Packer. 185 _, ok := h.buildNames[source.String()] 186 if !ok { 187 h.buildNames[source.String()] = struct{}{} 188 continue 189 } 190 191 conflictSources[source.String()] = struct{}{} 192 // We need to delete it to avoid having a false-positive 193 // when returning the name, since we'll be using 194 // the combination of build name + source.String() 195 delete(h.buildNames, source.String()) 196 } 197 } 198 199 // Second pass is to take care of conflicting sources 200 // 201 // If the same source is used twice in the configuration, we need to 202 // have a way to differentiate the two on HCP, as each build should have 203 // a locally unique name. 204 // 205 // If that happens, we then use a combination of both the build name, and 206 // the source type. 207 for _, build := range h.configuration.Builds { 208 for _, source := range build.Sources { 209 if _, ok := conflictSources[source.String()]; !ok { 210 continue 211 } 212 213 buildName := source.String() 214 if build.Name != "" { 215 buildName = fmt.Sprintf("%s.%s", build.Name, buildName) 216 } 217 218 if _, ok := h.buildNames[buildName]; ok { 219 diags = append(diags, &hcl.Diagnostic{ 220 Severity: hcl.DiagError, 221 Summary: "Build name conflicts", 222 Subject: &build.HCL2Ref.DefRange, 223 Detail: fmt.Sprintf("Two sources are used in the same build block, causing "+ 224 "a conflict, there must only be one instance of %s", source.String()), 225 }) 226 } 227 h.buildNames[buildName] = struct{}{} 228 } 229 } 230 231 if diags.HasErrors() { 232 return diags 233 } 234 235 for buildName := range h.buildNames { 236 h.bucket.RegisterBuildForComponent(buildName) 237 } 238 return diags 239 } 240 241 func (h *HCLRegistry) Metadata() Metadata { 242 return h.metadata 243 } 244 245 // HCPBuildName will return the properly formatted string taking name conflict into account 246 func (h *HCLRegistry) HCPBuildName(build *packer.CoreBuild) string { 247 _, ok := h.buildNames[build.Type] 248 if ok { 249 return build.Type 250 } 251 252 return fmt.Sprintf("%s.%s", build.BuildName, build.Type) 253 }