Skip to content

Commit

Permalink
feat: host builder metadata envs (#2195)
Browse files Browse the repository at this point in the history
  • Loading branch information
lkingland authored Mar 1, 2024
1 parent 7bf3e10 commit ab2bbff
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 1 deletion.
89 changes: 89 additions & 0 deletions pkg/oci/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,92 @@ type fileInfo struct {
Executable bool
Linkname string
}

// TestBuilder_StaticEnvs ensures that certain "static" environment variables
// comprising Function metadata are added to the config.
func TestBuilder_StaticEnvs(t *testing.T) {
root, done := Mktemp(t)
defer done()

staticEnvs := []string{
"FUNC_CREATED",
"FUNC_VERSION",
}

f, err := fn.New().Init(fn.Function{Root: root, Runtime: "go"})
if err != nil {
t.Fatal(err)
}

if err := NewBuilder("", true).Build(context.Background(), f, TestPlatforms); err != nil {
t.Fatal(err)
}

// Assert
// Check if the OCI container defines at least one of the static
// variables on each of the constituent containers.
// ---
// Get the images list (manifest descripors) from the index
ociPath := path(f.Root, fn.RunDataDir, "builds", "last", "oci")
data, err := os.ReadFile(filepath.Join(ociPath, "index.json"))
if err != nil {
t.Fatal(err)
}
var index struct {
Manifests []struct {
Digest string `json:"digest"`
} `json:"manifests"`
}
if err := json.Unmarshal(data, &index); err != nil {
t.Fatal(err)
}
for _, manifestDesc := range index.Manifests {

// Dereference the manifest descriptor into the referenced image manifest
manifestHash := strings.TrimPrefix(manifestDesc.Digest, "sha256:")
data, err := os.ReadFile(filepath.Join(ociPath, "blobs", "sha256", manifestHash))
if err != nil {
t.Fatal(err)
}
var manifest struct {
Config struct {
Digest string `json:"digest"`
} `json:"config"`
}
if err := json.Unmarshal(data, &manifest); err != nil {
t.Fatal(err)
}

// From the image manifest get the image's config.json
configHash := strings.TrimPrefix(manifest.Config.Digest, "sha256:")
data, err = os.ReadFile(filepath.Join(ociPath, "blobs", "sha256", configHash))
if err != nil {
t.Fatal(err)
}
var config struct {
Config struct {
Env []string `json:"Env"`
} `json:"config"`
}
if err := json.Unmarshal(data, &config); err != nil {
panic(err)
}

containsEnv := func(ss []string, name string) bool {
for _, s := range ss {
if strings.HasPrefix(s, name) {
return true
}
}
return false
}

for _, expected := range staticEnvs {
t.Logf("checking for %q in slice %v", expected, config.Config.Env)
if containsEnv(config.Config.Env, expected) {
continue // to check the rest
}
t.Fatalf("static env %q not found in resultant container", expected)
}
}
}
44 changes: 43 additions & 1 deletion pkg/oci/containerize.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"io"
"io/fs"
"os"
"os/exec"
slashpath "path"
"path/filepath"
"strings"
"time"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/tarball"
Expand Down Expand Up @@ -428,7 +430,7 @@ func newConfig(cfg *buildConfig, p v1.Platform, layers ...v1.Layer) (desc v1.Des
Variant: p.Variant,
Config: v1.Config{
ExposedPorts: map[string]struct{}{"8080/tcp": {}},
Env: cfg.f.Run.Envs.Slice(),
Env: newConfigEnvs(cfg),
Cmd: []string{"/func/f"}, // NOTE: Using Cmd because Entrypoint can not be overridden
WorkingDir: "/func/",
StopSignal: "SIGKILL",
Expand Down Expand Up @@ -482,6 +484,46 @@ func newConfig(cfg *buildConfig, p v1.Platform, layers ...v1.Layer) (desc v1.Des
return
}

// newConfigEnvs returns the final set of environment variables to build into
// the container. This consists of func-provided build metadata envs as well
// as any environment variables provided on the function itself.
func newConfigEnvs(cfg *buildConfig) []string {
envs := []string{}

// FUNC_CREATED
// Formats container timestamp as RFC3339; a stricter version of the ISO 8601
// format used by the container image manifest's 'Created' attribute.
envs = append(envs, "FUNC_CREATED="+cfg.t.Format(time.RFC3339))

// FUNC_VERSION
// If source controlled, and if being built from a system with git, the
// environment FUNC_VERSION will be populated. Otherwise it will exist
// (to indicate this logic was executed) but have an empty value.
if cfg.verbose {
fmt.Printf("cd %v && export FUNC_VERSION=$(git describe --tags)\n", cfg.f.Root)
}
cmd := exec.CommandContext(cfg.ctx, "git", "describe", "--tags")
cmd.Dir = cfg.f.Root
output, err := cmd.Output()
if err != nil {
if cfg.verbose {
fmt.Fprintf(os.Stderr, "unable to determine function version. %v", err)
}
envs = append(envs, "FUNC_VERSION=")
} else {
envs = append(envs, "FUNC_VERSION="+strings.TrimSpace(string(output)))
}

// TODO: OTHERS?
// Other metadata that may be useful. Perhaps:
// - func client version (func cli) used when building this file?
// - user/environment which triggered this build?
// - A reflection of the function itself? Image, registry, etc. etc?

// ENVs defined on the Function
return append(envs, cfg.f.Run.Envs.Slice()...)
}

func newImageIndex(cfg *buildConfig, imageDescs []v1.Descriptor) (index v1.IndexManifest, err error) {
index = v1.IndexManifest{
SchemaVersion: 2,
Expand Down

0 comments on commit ab2bbff

Please sign in to comment.