Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: host builder metadata envs #2195

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading