From 03ee76ee5e156513b1fa1493219f12eb823185df Mon Sep 17 00:00:00 2001 From: Devashish Date: Tue, 2 Jul 2024 18:15:21 +0530 Subject: [PATCH] Add build agnostic metadata --- command/build.go | 1 + command/cli.go | 25 +++++++ internal/hcp/registry/hcl.go | 6 ++ internal/hcp/registry/json.go | 7 ++ internal/hcp/registry/null_registry.go | 4 ++ internal/hcp/registry/registry.go | 1 + internal/hcp/registry/types.metadata_store.go | 66 +++++++++++++++++++ 7 files changed, 110 insertions(+) create mode 100644 internal/hcp/registry/types.metadata_store.go diff --git a/command/build.go b/command/build.go index 330e05e64..839dbca58 100644 --- a/command/build.go +++ b/command/build.go @@ -113,6 +113,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int if ret != 0 { return ret } + hcpRegistry.Metadata().Gather(cla.Args()) defer hcpRegistry.VersionStatusSummary() diff --git a/command/cli.go b/command/cli.go index 0cd82c274..dc169aa28 100644 --- a/command/cli.go +++ b/command/cli.go @@ -96,6 +96,31 @@ func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) { ba.MetaArgs.AddFlagSets(flags) } +// Args returns the list of arguments for HCP. +// +// Most of the arguments are kept as-is, except for the -var args, where only +// the keys are kept to avoid leaking potential secrets. +func (ba *BuildArgs) Args() map[string]interface{} { + cleanedArgs := make(map[string]interface{}) + cleanedArgs["debug"] = ba.Debug + cleanedArgs["force"] = ba.Force + + cleanedArgs["only"] = ba.Only + cleanedArgs["except"] = ba.Except + + cleanedArgs["var-files"] = ba.VarFiles + + var args []string + for k := range ba.Vars { + args = append(args, k) + } + cleanedArgs["vars"] = args + + cleanedArgs["path"] = ba.Path + + return cleanedArgs +} + // BuildArgs represents a parsed cli line for a `packer build` type BuildArgs struct { MetaArgs diff --git a/internal/hcp/registry/hcl.go b/internal/hcp/registry/hcl.go index d80577b13..208f4d437 100644 --- a/internal/hcp/registry/hcl.go +++ b/internal/hcp/registry/hcl.go @@ -21,6 +21,7 @@ type HCLRegistry struct { configuration *hcl2template.PackerConfig bucket *Bucket ui sdkpacker.Ui + metadata *MetadataStore } const ( @@ -164,5 +165,10 @@ func NewHCLRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLReg configuration: config, bucket: bucket, ui: ui, + metadata: &MetadataStore{}, }, nil } + +func (h *HCLRegistry) Metadata() Metadata { + return h.metadata +} diff --git a/internal/hcp/registry/json.go b/internal/hcp/registry/json.go index e94f7afd4..b180f1e2a 100644 --- a/internal/hcp/registry/json.go +++ b/internal/hcp/registry/json.go @@ -20,6 +20,7 @@ type JSONRegistry struct { configuration *packer.Core bucket *Bucket ui sdkpacker.Ui + metadata *MetadataStore } func NewJSONRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONRegistry, hcl.Diagnostics) { @@ -52,6 +53,7 @@ func NewJSONRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONRegistry, hcl.D configuration: config, bucket: bucket, ui: ui, + metadata: &MetadataStore{}, }, nil } @@ -107,3 +109,8 @@ func (h *JSONRegistry) CompleteBuild( func (h *JSONRegistry) VersionStatusSummary() { h.bucket.Version.statusSummary(h.ui) } + +// Metadata gets the global metadata object that registers global settings +func (h *JSONRegistry) Metadata() Metadata { + return h.metadata +} diff --git a/internal/hcp/registry/null_registry.go b/internal/hcp/registry/null_registry.go index 4f32c3d12..9767e8414 100644 --- a/internal/hcp/registry/null_registry.go +++ b/internal/hcp/registry/null_registry.go @@ -30,3 +30,7 @@ func (r nullRegistry) CompleteBuild( } func (r nullRegistry) VersionStatusSummary() {} + +func (r nullRegistry) Metadata() Metadata { + return NilMetadata{} +} diff --git a/internal/hcp/registry/registry.go b/internal/hcp/registry/registry.go index 56b70285d..e1c0ca5eb 100644 --- a/internal/hcp/registry/registry.go +++ b/internal/hcp/registry/registry.go @@ -19,6 +19,7 @@ type Registry interface { StartBuild(context.Context, sdkpacker.Build) error CompleteBuild(ctx context.Context, build sdkpacker.Build, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error) VersionStatusSummary() + Metadata() Metadata } // New instantiates the appropriate registry for the Packer configuration template type. diff --git a/internal/hcp/registry/types.metadata_store.go b/internal/hcp/registry/types.metadata_store.go new file mode 100644 index 000000000..a2ddc1d93 --- /dev/null +++ b/internal/hcp/registry/types.metadata_store.go @@ -0,0 +1,66 @@ +package registry + +import ( + "runtime" +) + +// Metadata is the global metadata store, it is attached to a registry implementation +// and keeps track of the environmental information. +// This then can be sent to HCP Packer, so we can present it to users. +type Metadata interface { + // Gather is the point where we vacuum all the information + // relevant from the environment in order to expose it to HCP Packer. + Gather(args map[string]interface{}) error + // Render is called when the metadata is sent to HCP Packer, + // i.e. when a build finishes, so it gets merged with the rest of the + // information that is build-specific + Render() map[string]interface{} +} + +// MetadataStore is the effective implementation of a global store for metadata +// destined to be uploaded to HCP Packer. +// +// If HCP is enabled during a build, this is populated with a curated list of +// arguments to the build command, and environment-related information. +type MetadataStore struct { + PackerBuildCommandOptions map[string]interface{} + OperatingSystem map[string]string +} + +func (ms *MetadataStore) Gather(args map[string]interface{}) error { + // Environment data + ms.gatherOperatingSystemDetails() + + // Build arguments + ms.PackerBuildCommandOptions = args + + return nil +} + +func (ms *MetadataStore) gatherOperatingSystemDetails() { + ms.OperatingSystem = map[string]string{ + "os": runtime.GOOS, + "arch": runtime.GOARCH, + } +} + +func (ms *MetadataStore) Render() map[string]interface{} { + return map[string]interface{}{ + "operating_system": ms.OperatingSystem, + "packer_build_command_options": ms.PackerBuildCommandOptions, + } +} + +// NilMetadata is a dummy implementation of a Metadata that does nothing. +// +// It is the implementation used typically when HCP is disabled, so nothing is +// collected or kept in memory in this case. +type NilMetadata struct{} + +func (ns NilMetadata) Gather(args map[string]interface{}) error { + return nil +} + +func (ns NilMetadata) Render() map[string]interface{} { + return map[string]interface{}{} +}