Skip to content

Commit

Permalink
Merge pull request #13113 from hashicorp/backport/feature/build-metad…
Browse files Browse the repository at this point in the history
…ata-phase-2/entirely-up-ghoul

This pull request was automerged via backport-assistant
  • Loading branch information
hc-github-team-packer authored Jul 22, 2024
2 parents a8bcc23 + e104127 commit 761f638
Show file tree
Hide file tree
Showing 12 changed files with 487 additions and 7 deletions.
1 change: 1 addition & 0 deletions command/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
if ret != 0 {
return ret
}
hcpRegistry.Metadata().Gather(GetCleanedBuildArgs(cla))

defer hcpRegistry.VersionStatusSummary()

Expand Down
24 changes: 24 additions & 0 deletions command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) {
ba.MetaArgs.AddFlagSets(flags)
}

// GetCleanedBuildArgs returns a map containing build flags specified to build for tracking within
// the HCP Packer registry.
//
// 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 GetCleanedBuildArgs(ba *BuildArgs) map[string]interface{} {
cleanedArgs := map[string]interface{}{
"debug": ba.Debug,
"force": ba.Force,
"only": ba.Only,
"except": ba.Except,
"var-files": ba.VarFiles,
"path": ba.Path,
}

var varNames []string
for k := range ba.Vars {
varNames = append(varNames, k)
}
cleanedArgs["vars"] = varNames

return cleanedArgs
}

// BuildArgs represents a parsed cli line for a `packer build`
type BuildArgs struct {
MetaArgs
Expand Down
10 changes: 8 additions & 2 deletions internal/hcp/registry/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type HCLRegistry struct {
configuration *hcl2template.PackerConfig
bucket *Bucket
ui sdkpacker.Ui
metadata *MetadataStore
}

const (
Expand Down Expand Up @@ -87,8 +88,8 @@ func (h *HCLRegistry) CompleteBuild(
buildName = cb.Type
}

metadata := cb.GetMetadata()
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, metadata)
buildMetadata, envMetadata := cb.GetMetadata(), h.metadata
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata, envMetadata)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -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
}
11 changes: 9 additions & 2 deletions internal/hcp/registry/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -52,6 +53,7 @@ func NewJSONRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONRegistry, hcl.D
configuration: config,
bucket: bucket,
ui: ui,
metadata: &MetadataStore{},
}, nil
}

Expand Down Expand Up @@ -95,8 +97,8 @@ func (h *JSONRegistry) CompleteBuild(
buildErr error,
) ([]sdkpacker.Artifact, error) {
buildName := build.Name()
buildMetadata := build.(*packer.CoreBuild).GetMetadata()
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata)
buildMetadata, envMetadata := build.(*packer.CoreBuild).GetMetadata(), h.metadata
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata, envMetadata)
if err != nil {
return nil, err
}
Expand All @@ -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
}
104 changes: 104 additions & 0 deletions internal/hcp/registry/metadata/cicd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package metadata

import (
"fmt"
"os"
)

type GithubActions struct{}

func (g *GithubActions) Detect() error {
_, ok := os.LookupEnv("GITHUB_ACTIONS")
if !ok {
return fmt.Errorf("GITHUB_ACTIONS environment variable not found")
}
return nil
}

func (g *GithubActions) Details() map[string]interface{} {
env := make(map[string]interface{})
keys := []string{
"GITHUB_REPOSITORY",
"GITHUB_REPOSITORY_ID",
"GITHUB_WORKFLOW_URL",
"GITHUB_SHA",
"GITHUB_REF",
"GITHUB_ACTOR",
"GITHUB_ACTOR_ID",
"GITHUB_TRIGGERING_ACTOR",
"GITHUB_EVENT_NAME",
"GITHUB_JOB",
}

for _, key := range keys {
if value, ok := os.LookupEnv(key); ok {
env[key] = value
}
}

env["GITHUB_WORKFLOW_URL"] = fmt.Sprintf("%s/%s/actions/runs/%s", os.Getenv("GITHUB_SERVER_URL"), os.Getenv("GITHUB_REPOSITORY"), os.Getenv("GITHUB_RUN_ID"))
return env
}

func (g *GithubActions) Type() string {
return "github-actions"
}

type GitlabCI struct{}

func (g *GitlabCI) Detect() error {
_, ok := os.LookupEnv("GITLAB_CI")
if !ok {
return fmt.Errorf("GITLAB_CI environment variable not found")
}
return nil
}

func (g *GitlabCI) Details() map[string]interface{} {
env := make(map[string]interface{})
keys := []string{
"CI_PROJECT_NAME",
"CI_PROJECT_ID",
"CI_PROJECT_URL",
"CI_COMMIT_SHA",
"CI_COMMIT_REF_NAME",
"GITLAB_USER_NAME",
"GITLAB_USER_ID",
"CI_PIPELINE_SOURCE",
"CI_PIPELINE_URL",
"CI_JOB_URL",
"CI_SERVER_NAME",
"CI_REGISTRY_IMAGE",
}

for _, key := range keys {
if value, ok := os.LookupEnv(key); ok {
env[key] = value
}
}

return env
}

func (g *GitlabCI) Type() string {
return "gitlab-ci"
}

func GetCicdMetadata() map[string]interface{} {
cicd := []MetadataProvider{
&GithubActions{},
&GitlabCI{},
}

for _, c := range cicd {
err := c.Detect()
if err == nil {
return map[string]interface{}{
"type": c.Type(),
"details": c.Details(),
}
}
}

return nil
}
132 changes: 132 additions & 0 deletions internal/hcp/registry/metadata/os.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package metadata

import (
"log"
"os/exec"
"runtime"
"strings"
"time"
)

type OSInfo struct {
Name string
Arch string
Version string
}

// CommandExecutor is an interface for executing commands.
type CommandExecutor interface {
Exec(name string, arg ...string) ([]byte, error)
}

// DefaultExecutor is the default implementation of CommandExecutor.
type DefaultExecutor struct{}

// Exec executes a command and returns the combined output.
func (d DefaultExecutor) Exec(name string, arg ...string) ([]byte, error) {
cmd := exec.Command(name, arg...)
return cmd.CombinedOutput()
}

var executor CommandExecutor = DefaultExecutor{}

func GetOSMetadata() map[string]interface{} {
var osInfo OSInfo

switch runtime.GOOS {
case "windows":
osInfo = GetInfoForWindows(executor)
case "darwin":
osInfo = GetInfo(executor, "-srm")
case "linux":
osInfo = GetInfo(executor, "-srio")
case "freebsd":
osInfo = GetInfo(executor, "-sri")
case "openbsd":
osInfo = GetInfo(executor, "-srm")
case "netbsd":
osInfo = GetInfo(executor, "-srm")
default:
osInfo = OSInfo{
Name: runtime.GOOS,
Arch: runtime.GOARCH,
}
}

return map[string]interface{}{
"type": osInfo.Name,
"details": map[string]interface{}{
"arch": osInfo.Arch,
"version": osInfo.Version,
},
}
}

func GetInfo(exec CommandExecutor, flags string) OSInfo {
out, err := uname(exec, flags)
tries := 0
for strings.Contains(out, "broken pipe") && tries < 3 {
out, err = uname(exec, flags)
time.Sleep(500 * time.Millisecond)
tries++
}
if strings.Contains(out, "broken pipe") || err != nil {
out = ""
}

if err != nil {
log.Printf("[ERROR] failed to get the OS info: %s", err)
}
core := retrieveCore(out)
return OSInfo{
Name: runtime.GOOS,
Arch: runtime.GOARCH,
Version: core,
}
}

func uname(exec CommandExecutor, flags string) (string, error) {
output, err := exec.Exec("uname", flags)
return string(output), err
}

func retrieveCore(osStr string) string {
osStr = strings.Replace(osStr, "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
osInfo := strings.Split(osStr, " ")

var core string
if len(osInfo) > 1 {
core = osInfo[1]
}
return core
}

func GetInfoForWindows(exec CommandExecutor) OSInfo {
out, err := exec.Exec("cmd", "ver")
if err != nil {
log.Printf("[ERROR] failed to get the OS info: %s", err)
return OSInfo{
Name: runtime.GOOS,
Arch: runtime.GOARCH,
}
}

osStr := strings.Replace(string(out), "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
tmp1 := strings.Index(osStr, "[Version")
tmp2 := strings.Index(osStr, "]")
var ver string
if tmp1 == -1 || tmp2 == -1 {
ver = ""
} else {
ver = osStr[tmp1+9 : tmp2]
}

osInfo := OSInfo{
Name: runtime.GOOS,
Arch: runtime.GOARCH,
Version: ver,
}
return osInfo
}
Loading

0 comments on commit 761f638

Please sign in to comment.