Add OS, VCS and CICD metadata

feature/build-metadata-phase-2
Devashish 2 years ago committed by Lucas Bajolet
parent 749162e0c0
commit 73e0167a57

@ -88,8 +88,8 @@ func (h *HCLRegistry) CompleteBuild(
buildName = cb.Type
}
metadata := cb.GetMetadata()
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, metadata)
buildMetadata := cb.GetMetadata()
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata, h.metadata)
if err != nil {
return nil, err
}

@ -98,7 +98,7 @@ func (h *JSONRegistry) CompleteBuild(
) ([]sdkpacker.Artifact, error) {
buildName := build.Name()
buildMetadata := build.(*packer.CoreBuild).GetMetadata()
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata)
err := h.bucket.Version.AddMetadataToBuild(ctx, buildName, buildMetadata, h.metadata)
if err != nil {
return nil, err
}

@ -0,0 +1,103 @@
package registry
import (
"fmt"
"os"
)
type CICD interface {
Detect() bool
Env() map[string]string
Type() string
}
type GithubActions struct{}
func (g *GithubActions) Detect() bool {
_, ok := os.LookupEnv("GITHUB_ACTIONS")
return ok
}
func (g *GithubActions) Env() map[string]string {
env := make(map[string]string)
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() bool {
_, ok := os.LookupEnv("GITLAB_CI")
return ok
}
func (g *GitlabCI) Env() map[string]string {
env := make(map[string]string)
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 := []CICD{
&GithubActions{},
&GitlabCI{},
}
for _, c := range cicd {
if c.Detect() {
return map[string]interface{}{
"type": c.Type(),
"details": c.Env(),
}
}
}
return nil
}

@ -0,0 +1,129 @@
package registry
import (
"bytes"
"log"
"os/exec"
"runtime"
"strings"
"time"
)
type OSInfo struct {
Name string
Arch string
Version string
}
func GetOSMetadata() map[string]interface{} {
var osInfo OSInfo
switch runtime.GOOS {
case "windows":
osInfo = GetInfoForWindows()
case "darwin":
osInfo = GetInfo("-srm")
case "linux":
osInfo = GetInfo("-srio")
case "freebsd":
osInfo = GetInfo("-sri")
case "openbsd":
osInfo = GetInfo("-srm")
case "netbsd":
osInfo = GetInfo("-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(flags string) OSInfo {
out, err := _uname(flags)
tries := 0
for strings.Contains(out, "broken pipe") && tries < 3 {
out, err = _uname(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(flags string) (string, error) {
cmd := exec.Command("uname", flags)
cmd.Stdin = strings.NewReader("some input")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
return out.String(), 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() OSInfo {
cmd := exec.Command("cmd", "ver")
cmd.Stdin = strings.NewReader("some input")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
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(out.String(), "\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
}

@ -0,0 +1,93 @@
package registry
import (
"log"
"os"
gt "github.com/go-git/go-git/v5"
)
type VCS interface {
Detect(wd string) error
Details() map[string]interface{}
Type() string
}
type Git struct {
repo *gt.Repository
}
func (g *Git) Detect(wd string) error {
repo, err := gt.PlainOpenWithOptions(wd, &gt.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return err
}
g.repo = repo
return nil
}
func (g *Git) hasUncommittedChanges() bool {
worktree, err := g.repo.Worktree()
if err != nil {
log.Printf("[ERROR] failed to get the git worktree: %s", err)
return false
}
status, err := worktree.Status()
if err != nil {
log.Printf("[ERROR] failed to get the git worktree status: %s", err)
return false
}
return !status.IsClean()
}
func (g *Git) Type() string {
return "git"
}
func (g *Git) Details() map[string]interface{} {
resp := map[string]interface{}{}
headRef, err := g.repo.Head()
if err != nil {
log.Printf("[ERROR] failed to get the git branch name: %s", err)
} else {
resp["ref"] = headRef.Name().Short()
}
commit, err := g.repo.CommitObject(headRef.Hash())
if err != nil {
log.Printf("[ERROR] failed to get the git commit hash: %s", err)
} else {
resp["commit"] = commit.Hash.String()
resp["author"] = commit.Author.Name + " <" + commit.Author.Email + ">"
}
resp["has_uncommitted_changes"] = g.hasUncommittedChanges()
return resp
}
func GetVcsMetadata() map[string]interface{} {
wd, err := os.Getwd()
if err != nil {
log.Printf("[ERROR] unable to retrieve current directory: %s", err)
return map[string]interface{}{}
}
vcsSystems := []VCS{
&Git{},
}
for _, vcs := range vcsSystems {
err := vcs.Detect(wd)
if err == nil {
return map[string]interface{}{
"type": vcs.Type(),
"details": vcs.Details(),
}
}
}
return nil
}

@ -1,20 +1,12 @@
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{}
Gather(args map[string]interface{})
}
// MetadataStore is the effective implementation of a global store for metadata
@ -24,31 +16,16 @@ type Metadata interface {
// arguments to the build command, and environment-related information.
type MetadataStore struct {
PackerBuildCommandOptions map[string]interface{}
OperatingSystem map[string]string
OperatingSystem map[string]interface{}
Vcs map[string]interface{}
Cicd map[string]interface{}
}
func (ms *MetadataStore) Gather(args map[string]interface{}) error {
// Environment data
ms.gatherOperatingSystemDetails()
// Build arguments
func (ms *MetadataStore) Gather(args map[string]interface{}) {
ms.OperatingSystem = GetOSMetadata()
ms.Cicd = GetCicdMetadata()
ms.Vcs = GetVcsMetadata()
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.
@ -57,10 +34,4 @@ func (ms *MetadataStore) Render() map[string]interface{} {
// 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{}{}
}
func (ns NilMetadata) Gather(args map[string]interface{}) {}

@ -179,7 +179,7 @@ func (version *Version) statusSummary(ui sdkpacker.Ui) {
// AddMetadataToBuild adds metadata to a build in the HCP Packer registry.
func (version *Version) AddMetadataToBuild(
ctx context.Context, buildName string, metadata packer.BuildMetadata,
ctx context.Context, buildName string, buildMetadata packer.BuildMetadata, globalMetadata *MetadataStore,
) error {
buildToUpdate, err := version.Build(buildName)
if err != nil {
@ -187,10 +187,10 @@ func (version *Version) AddMetadataToBuild(
}
packerMetadata := make(map[string]interface{})
packerMetadata["version"] = metadata.PackerVersion
packerMetadata["version"] = buildMetadata.PackerVersion
var pluginsMetadata []map[string]interface{}
for _, plugin := range metadata.Plugins {
for _, plugin := range buildMetadata.Plugins {
pluginMetadata := map[string]interface{}{
"version": plugin.Description.Version,
"name": plugin.Name,
@ -198,7 +198,12 @@ func (version *Version) AddMetadataToBuild(
pluginsMetadata = append(pluginsMetadata, pluginMetadata)
}
packerMetadata["plugins"] = pluginsMetadata
packerMetadata["options"] = globalMetadata.PackerBuildCommandOptions
packerMetadata["os"] = globalMetadata.OperatingSystem
buildToUpdate.Metadata.Packer = packerMetadata
buildToUpdate.Metadata.Vcs = globalMetadata.Vcs
buildToUpdate.Metadata.Cicd = globalMetadata.Cicd
return nil
}

Loading…
Cancel
Save