Skip to content

Commit

Permalink
feat: function context for deploy command
Browse files Browse the repository at this point in the history
  • Loading branch information
lkingland committed Nov 16, 2022
1 parent 1a40cc4 commit 0a3cb6a
Show file tree
Hide file tree
Showing 7 changed files with 540 additions and 483 deletions.
10 changes: 5 additions & 5 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ and the image name is stored in the configuration file.

// Flags
//
// NOTE on falag defaults:
// NOTE on flag defaults:
// Use the config value when available, as this will include global static
// defaults, user settings and the value from the function with context.
// Use the function struct for flag flags which are not globally configurable
Expand All @@ -77,11 +77,11 @@ and the image name is stored in the configuration file.
// Options whose value may be defined globally may also exist on the
// contextually relevant function; sets are flattened above via cfg.Apply(f)
cmd.Flags().StringP("builder", "b", cfg.Builder,
fmt.Sprintf("build strategy to use when creating the underlying image. Currently supported build strategies are %s.", KnownBuilders()))
fmt.Sprintf("Builder to use when creating the function's container. Currently supported builders are %s. (E$FUNC_BUILDER)", KnownBuilders()))
cmd.Flags().BoolP("confirm", "c", cfg.Confirm,
"Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)")
cmd.Flags().StringP("registry", "r", cfg.Registry,
"Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined (Env: $FUNC_REGISTRY)")
"Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. (Env: $FUNC_REGISTRY)")

// Function-Context Flags:
// Options whose value is available on the function with context only
Expand Down Expand Up @@ -133,7 +133,7 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
if err != nil {
return
}
f = cfg.Configure(f) // Updates f at path to include buil request values
f = cfg.Configure(f) // Updates f at path to include build request values

// Checks if there is a difference between defined registry and its value used as a prefix in the image tag
// In case of a mismatch a new image tag is created and used for build
Expand Down Expand Up @@ -235,7 +235,7 @@ func newBuildConfig() buildConfig {

// Configure the given function. Updates a function struct with all
// configurable values. Note that buildConfig already includes function's
// current values, as they were passed through vi flag defaults, so overwriting
// current values, as they were passed through via flag defaults, so overwriting
// is a noop.
func (c buildConfig) Configure(f fn.Function) fn.Function {
f = c.Global.Configure(f)
Expand Down
227 changes: 5 additions & 222 deletions cmd/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,193 +2,22 @@ package cmd

import (
"errors"
"fmt"
"testing"

fn "knative.dev/func"
"knative.dev/func/builders"
"knative.dev/func/mock"
)

// TestBuild_ConfigApplied ensures that the build command applies config
// settings at each level (static, global, function, envs, flags).
func TestBuild_ConfigApplied(t *testing.T) {
var (
err error
home = fmt.Sprintf("%s/testdata/TestBuild_ConfigApplied", cwd())
root = fromTempDirectory(t)
f = fn.Function{Runtime: "go", Root: root, Name: "f"}
pusher = mock.NewPusher()
clientFn = NewTestClient(fn.WithPusher(pusher))
)
t.Setenv("XDG_CONFIG_HOME", home)

if err = fn.New().Create(f); err != nil {
t.Fatal(err)
}

// Ensure the global config setting was loaded: Registry
// global config in ./testdata/TestBuild_ConfigApplied sets registry
if err = NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Registry != "registry.example.com/alice" {
t.Fatalf("expected registry 'registry.example.com/alice' got '%v'", f.Registry)
}

// Ensure flags are evaluated
cmd := NewBuildCmd(clientFn)
cmd.SetArgs([]string{"--builder-image", "example.com/builder/image:v1.2.3"})
if err = cmd.Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Build.BuilderImages[f.Build.Builder] != "example.com/builder/image:v1.2.3" {
t.Fatalf("expected builder image not set. Flags not evaluated? got %v", f.Build.BuilderImages[f.Build.Builder])
}

// Ensure function context loaded
// Update registry on the function and ensure it takes precidence (overrides)
// the global setting defined in home.
f.Registry = "registry.example.com/charlie"
if err := f.Write(); err != nil {
t.Fatal(err)
}
if err := NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Image != "registry.example.com/charlie/f:latest" {
t.Fatalf("expected image 'registry.example.com/charlie/f:latest' got '%v'", f.Image)
}

// Ensure environment variables loaded: Push
// Test environment variable evaluation using FUNC_PUSH
t.Setenv("FUNC_PUSH", "true")
if err := NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if !pusher.PushInvoked {
t.Fatalf("push was not invoked when FUNC_PUSH=true")
}

testConfigApplied(NewBuildCmd, t)
}

// TestBuild_ConfigPrecidence ensures that the correct precidence for config
// TestBuild_ConfigPrecedence ensures that the correct precidence for config
// are applied: static < global < function context < envs < flags
func TestBuild_ConfigPrecidence(t *testing.T) {
var (
err error
home = fmt.Sprintf("%s/testdata/TestBuild_ConfigPrecidence", cwd())
builder = mock.NewBuilder()
clientFn = NewTestClient(fn.WithBuilder(builder))
)

// Ensure static default applied via 'builder'
// (a degenerate case, mostly just ensuring config values are not wiped to a
// zero value struct, etc)
root := fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry.example.com/global
f := fn.Function{Runtime: "go", Root: root, Name: "f"}
if err = fn.New().Create(f); err != nil {
t.Fatal(err)
}
if err := NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Build.Builder != builders.Default {
t.Fatalf("expected static default builder '%v', got '%v'", builders.Default, f.Build.Builder)
}

// Ensure Global Config applied via config in ./testdata
root = fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry.example.com/global
f = fn.Function{Runtime: "go", Root: root, Name: "f"}
if err := fn.New().Create(f); err != nil {
t.Fatal(err)
}
if err = NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Registry != "registry.example.com/global" { // from ./testdata
t.Fatalf("expected registry 'example.com/global', got '%v'", f.Registry)
}

// Ensure Function context overrides global config
// The stanza above ensures the global config is applied. This stanza
// ensures that, if set on the function, it will take precidence.
root = fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry=example.com/global
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
t.Fatal(err)
}
if err = NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Registry != "example.com/function" {
t.Fatalf("expected function's value for registry of 'example.com/function' to override global config setting of 'example.com/global', but got '%v'", f.Registry)
}

// Ensure Environment Variable overrides function context.
root = fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry.example.com/global
t.Setenv("FUNC_REGISTRY", "example.com/env")
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
t.Fatal(err)
}
if err := NewBuildCmd(clientFn).Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Registry != "example.com/env" {
t.Fatalf("expected FUNC_REGISTRY=example.com/env to override function's value of 'example.com/function', but got '%v'", f.Registry)
}

// Ensure flags override environment variables.
root = fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry=example.com/global
t.Setenv("FUNC_REGISTRY", "example.com/env")
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
t.Fatal(err)
}
cmd := NewBuildCmd(clientFn)
cmd.SetArgs([]string{"--registry=example.com/flag"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
if f, err = fn.NewFunction(root); err != nil {
t.Fatal(err)
}
if f.Registry != "example.com/flag" {
t.Fatalf("expected flag 'example.com/flag' to take precidence over env var, but got '%v'", f.Registry)
}
func TestBuild_ConfigPrecedence(t *testing.T) {
testConfigPrecedence(NewBuildCmd, t)
}

// TestBuild_ImageFlag ensures that the image flag is used when specified.
Expand Down Expand Up @@ -417,51 +246,5 @@ func TestBuild_Registry(t *testing.T) {
// to the current command execution is loaded and used for flag defaults by
// spot-checking the builder setting.
func TestBuild_FunctionContext(t *testing.T) {
root := fromTempDirectory(t)

if err := fn.New().Create(fn.Function{Runtime: "go", Root: root, Registry: TestRegistry}); err != nil {
t.Fatal(err)
}

// Build the function explicitly setting the builder to !builders.Default
cmd := NewBuildCmd(NewTestClient())
dflt := cmd.Flags().Lookup("builder").DefValue

// The initial default value should be builders.Default (see global config)
if dflt != builders.Default {
t.Fatalf("expected flag default value '%v', got '%v'", builders.Default, dflt)
}

// Choose the value that is not the default
// We must calculate this because downstream changes the default via patches.
var builder string
if builders.Default == builders.Pack {
builder = builders.S2I
} else {
builder = builders.Pack
}

// Build with the other
cmd.SetArgs([]string{"--builder", builder})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}

// The function should now have the builder set to the new builder
f, err := fn.NewFunction(root)
if err != nil {
t.Fatal(err)
}
if f.Build.Builder != builder {
t.Fatalf("expected function to have new builder '%v', got '%v'", builder, f.Build.Builder)
}

// The command default should now take into account the function when
// determining the flag default
cmd = NewBuildCmd(NewTestClient())
dflt = cmd.Flags().Lookup("builder").DefValue

if dflt != builder {
t.Fatalf("expected flag default to be function's current builder '%v', got '%v'", builder, dflt)
}
testFunctionContext(NewBuildCmd, t)
}
Loading

0 comments on commit 0a3cb6a

Please sign in to comment.