Skip to content
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
16 changes: 14 additions & 2 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ EXAMPLES
`,
SuggestFor: []string{"biuld", "buidl", "built"},
PreRunE: bindEnv("image", "path", "builder", "registry", "confirm",
"push", "builder-image", "platform", "verbose", "build-timestamp",
"registry-insecure", "username", "password", "token"),
"push", "builder-image", "base-image", "platform", "verbose",
"build-timestamp", "registry-insecure", "username", "password", "token"),
RunE: func(cmd *cobra.Command, args []string) error {
return runBuild(cmd, args, newClient)
},
Expand Down Expand Up @@ -110,6 +110,8 @@ EXAMPLES
builderImage := f.Build.BuilderImages[f.Build.Builder]
cmd.Flags().StringP("builder-image", "", builderImage,
"Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().StringP("base-image", "", f.Build.BaseImage,
"Override the base image for your function (host builder only)")
cmd.Flags().StringP("image", "i", f.Image,
"Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry ($FUNC_IMAGE)")

Expand Down Expand Up @@ -223,6 +225,10 @@ type buildConfig struct {
// image name derivation based on registry and function name)
Image string

// BaseImage is an image to build a function upon (host builder only)
// TODO: gauron99 -- make option to add a path to dockerfile ?
BaseImage string

// Path of the function implementation on local disk. Defaults to current
// working directory of the process.
Path string
Expand Down Expand Up @@ -260,6 +266,7 @@ func newBuildConfig() buildConfig {
RegistryInsecure: viper.GetBool("registry-insecure"),
},
BuilderImage: viper.GetString("builder-image"),
BaseImage: viper.GetString("base-image"),
Image: viper.GetString("image"),
Path: viper.GetString("path"),
Platform: viper.GetString("platform"),
Expand All @@ -281,6 +288,7 @@ func (c buildConfig) Configure(f fn.Function) fn.Function {
f.Build.BuilderImages[f.Build.Builder] = c.BuilderImage
}
f.Image = c.Image
f.Build.BaseImage = c.BaseImage
// Path, Platform and Push are not part of a function's state.
return f
}
Expand Down Expand Up @@ -360,6 +368,10 @@ func (c buildConfig) Validate() (err error) {
return
}

// BaseImage is only supported with the host builder
if c.BaseImage != "" && c.Builder != "host" {
err = errors.New("only host builds support specifying the base image")
}
return
}

Expand Down
6 changes: 6 additions & 0 deletions cmd/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ func TestBuild_Authentication(t *testing.T) {
testAuthentication(NewBuildCmd, t)
}

// TestBuild_BaseImage ensures that base image is used only with the right
// builders and propagates into f.Build.BaseImage
func TestBuild_BaseImage(t *testing.T) {
testBaseImage(NewBuildCmd, t)
}

// TestBuild_Push ensures that the build command properly pushes and respects
// the --push flag.
// - Push triggered after a successful build
Expand Down
8 changes: 7 additions & 1 deletion cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ EXAMPLES

`,
SuggestFor: []string{"delpoy", "deplyo"},
PreRunE: bindEnv("build", "build-timestamp", "builder", "builder-image", "confirm", "domain", "env", "git-branch", "git-dir", "git-url", "image", "namespace", "path", "platform", "push", "pvc-size", "service-account", "registry", "registry-insecure", "remote", "username", "password", "token", "verbose", "remote-storage-class"),
PreRunE: bindEnv("build", "build-timestamp", "builder", "builder-image",
"base-image", "confirm", "domain", "env", "git-branch", "git-dir",
"git-url", "image", "namespace", "path", "platform", "push", "pvc-size",
"service-account", "registry", "registry-insecure", "remote",
"username", "password", "token", "verbose", "remote-storage-class"),
RunE: func(cmd *cobra.Command, args []string) error {
return runDeploy(cmd, newClient)
},
Expand Down Expand Up @@ -163,6 +167,8 @@ EXAMPLES
builderImage := f.Build.BuilderImages[f.Build.Builder]
cmd.Flags().String("builder-image", builderImage,
"Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().StringP("base-image", "", f.Build.BaseImage,
"Override the base image for your function (host builder only)")
cmd.Flags().StringP("image", "i", f.Image,
"Full image name in the form [registry]/[namespace]/[name]:[tag]@[digest]. This option takes precedence over --registry. Specifying digest is optional, but if it is given, 'build' and 'push' phases are disabled. ($FUNC_IMAGE)")

Expand Down
72 changes: 72 additions & 0 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2170,3 +2170,75 @@ func Test_isDigested(t *testing.T) {
t.Fatal("did not report image reference has digest")
}
}

func TestDeploy_BaseImage(t *testing.T) {
testBaseImage(NewDeployCmd, t)
}

func testBaseImage(cmdFn commandConstructor, t *testing.T) {
const baseImage = "example.com/repo/baseImage"
tests := []struct {
name string
runtime string
builder string
expErr bool
}{
{
name: "should-succeed: python-runtime with host-builder",
runtime: "python",
builder: "host",
},
{
name: "should-succeed: go-runtime with host-builder",
runtime: "go",
builder: "host",
},
{
name: "should-fail: python-runtime with pack-builder",
runtime: "python",
builder: "pack",
expErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := FromTempDirectory(t)

// func init
f := fn.Function{Runtime: tt.runtime, Root: root}
_, err := fn.New().Init(f)
if err != nil {
t.Fatal(err)
}

//create cmd
cmd := cmdFn(NewTestClient(
fn.WithBuilder(mock.NewBuilder()),
fn.WithDeployer(mock.NewDeployer()),
fn.WithRegistry(TestRegistry),
))

// create flags for cmd
args := []string{
fmt.Sprintf("--builder=%s", tt.builder),
fmt.Sprintf("--base-image=%s", baseImage),
}

cmd.SetArgs(args)
err = cmd.Execute()

// ASSERT

// got error but expected success
if err != nil && !tt.expErr {
err = fmt.Errorf("Expected the test to succeed but instead got: %w", err)
t.Fatal(err)
}

// succeeded but expected fail
if err == nil && tt.expErr {
t.Fatal(fmt.Errorf("Expected error but test succeeded"))
}
})
}
}
6 changes: 5 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ EXAMPLES
$ {{rootCmdUse}} run --json
`,
SuggestFor: []string{"rnu"},
PreRunE: bindEnv("build", "builder", "builder-image", "confirm", "container", "env", "image", "path", "registry", "start-timeout", "verbose", "address", "json"),
PreRunE: bindEnv("build", "builder", "builder-image", "base-image",
"confirm", "container", "env", "image", "path", "registry",
"start-timeout", "verbose", "address", "json"),
RunE: func(cmd *cobra.Command, _ []string) error {
return runRun(cmd, newClient)
},
Expand Down Expand Up @@ -109,6 +111,8 @@ EXAMPLES
builderImage := f.Build.BuilderImages[f.Build.Builder]
cmd.Flags().String("builder-image", builderImage,
"Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().StringP("base-image", "", f.Build.BaseImage,
"Override the base image for your function (host builder only)")
cmd.Flags().StringP("image", "i", f.Image,
"Full image name in the form [registry]/[namespace]/[name]:[tag]. This option takes precedence over --registry. Specifying tag is optional. ($FUNC_IMAGE)")
cmd.Flags().StringArrayP("env", "e", []string{},
Expand Down
86 changes: 86 additions & 0 deletions cmd/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,89 @@ func TestRun_Address(t *testing.T) {
t.Fatal(err)
}
}

// TestRun_BaseImage ensures that running func run --base-image with various
// other
func TestRun_BaseImage(t *testing.T) {
const baseImage = "example.com/repo/baseImage"
tests := []struct {
name string
runtime string
builder string
expectError bool
}{
{
name: "should-succeed: python-runtime with host-builder",
runtime: "python",
builder: "host",
},
{
name: "should-succeed: go-runtime with host-builder",
runtime: "go",
builder: "host",
},
{
name: "should-fail: python-runtime with pack-builder",
runtime: "python",
builder: "pack",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := FromTempDirectory(t)
runner := mock.NewRunner()

runner.RunFn = func(_ context.Context, f fn.Function, _ string, _ time.Duration) (*fn.Job, error) {
errs := make(chan error, 1)
stop := func() error { return nil }
return fn.NewJob(f, "127.0.0.1", "8080", errs, stop, false)
}

builder := mock.NewBuilder()
//if tt.expectError {
// builder.BuildFn = func(f fn.Function) error { return fmt.Errorf("expected error") }
//}

cmd := NewRunCmd(NewTestClient(
fn.WithRunner(runner),
fn.WithBuilder(builder),
fn.WithRegistry(TestRegistry),
))
args := []string{"--build=true", fmt.Sprintf("--builder=%s", tt.builder), fmt.Sprintf("--base-image=%s", baseImage)}
cmd.SetArgs(args)

// set test case's function instance
_, err := fn.New().Init(fn.Function{Root: root, Runtime: tt.runtime})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
runErrCh := make(chan error, 1)
go func() {
t0 := tt // capture tt into closure
_, err := cmd.ExecuteContextC(ctx)
if err != nil && t0.expectError {
// This is an expected error, so simply continue execution ignoring
// the error (send nil on the channel to release the parent routine
runErrCh <- nil
return
} else if err != nil {
runErrCh <- err // error not expected
return
}

// No errors, but an error was expected:
if t0.expectError {
runErrCh <- fmt.Errorf("Expected error but got '%v'\n", err)
}
close(runErrCh) // release the waiting parent process
}()
cancel() // trigger the return of cmd.ExecuteContextC in the routine
<-ctx.Done()
if err := <-runErrCh; err != nil { // wait for completion of assertions
t.Fatal(err)
}
})
}
}
1 change: 1 addition & 0 deletions docs/reference/func_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func build
### Options

```
--base-image string Override the base image for your function (host builder only)
--build-timestamp Use the actual time as the created time for the docker image. This is only useful for buildpacks builder.
-b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". ($FUNC_BUILDER) (default "pack")
--builder-image string Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)
Expand Down
1 change: 1 addition & 0 deletions docs/reference/func_deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func deploy
### Options

```
--base-image string Override the base image for your function (host builder only)
--build string[="true"] Build the function. [auto|true|false]. ($FUNC_BUILD) (default "auto")
--build-timestamp Use the actual time as the created time for the docker image. This is only useful for buildpacks builder.
-b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". (default "pack")
Expand Down
1 change: 1 addition & 0 deletions docs/reference/func_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func run

```
--address string Interface and port on which to bind and listen. Default is 127.0.0.1:8080, or an available port if 8080 is not available. ($FUNC_ADDRESS)
--base-image string Override the base image for your function (host builder only)
--build string[="true"] Build the function. [auto|true|false]. ($FUNC_BUILD) (default "auto")
-b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". (default "pack")
--builder-image string Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)
Expand Down
3 changes: 3 additions & 0 deletions pkg/functions/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ type BuildSpec struct {
// in .func/built-image
Image string `yaml:"-"`

// BaseImage defines an override for the function to be built upon (host bulder only)
BaseImage string `yaml:"baseImage,omitempty"`

// Mounts used in build phase. This is useful in particular for paketo bindings.
Mounts []MountSpec `yaml:"volumes,omitempty"`
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/oci/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type languageBuilder interface {
// Base returns the base image (if any) to use. Ideally this is a
// multi-arch base image with a corresponding platform image for
// each requested to be built.
Base() string
Base(customBase string) string

// WriteShared layers (not platform-specific) which need to be genearted
// on demand per language, such as shared dependencies.
Expand Down Expand Up @@ -568,12 +568,13 @@ func newCertsTarball(source, target string, verbose bool) error {
// Its layers are automatically downloaded into the local cache if this is
// the first fetch and their blobs linked into the final OCI image.
func pullBase(job buildJob, p v1.Platform) (image v1.Image, err error) {
if job.languageBuilder.Base() == "" {
baseImage := job.function.Build.BaseImage
if job.languageBuilder.Base(baseImage) == "" {
return // FROM SCRATCH
}

// Parse the base into a reference
ref, err := name.ParseReference(job.languageBuilder.Base())
ref, err := name.ParseReference(job.languageBuilder.Base(baseImage))
if err != nil {
return
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/oci/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func TestBuilder_StaticEnvs(t *testing.T) {
// OCI builder for each language, and can be overridden for testing
type TestLanguageBuilder struct {
BaseInvoked bool
BaseFn func() string
BaseFn func(customImage string) string

WriteSharedInvoked bool
WriteSharedFn func(buildJob) ([]imageLayer, error)
Expand All @@ -475,7 +475,7 @@ type TestLanguageBuilder struct {

func NewTestLanguageBuilder() *TestLanguageBuilder {
return &TestLanguageBuilder{
BaseFn: func() string { return "" },
BaseFn: func(customImage string) string { return "" },
WriteSharedFn: func(buildJob) ([]imageLayer, error) { return []imageLayer{}, nil },
WritePlatformFn: func(buildJob, v1.Platform) ([]imageLayer, error) { return []imageLayer{}, nil },
ConfigureFn: func(buildJob, v1.Platform, v1.ConfigFile) (v1.ConfigFile, error) {
Expand All @@ -484,9 +484,9 @@ func NewTestLanguageBuilder() *TestLanguageBuilder {
}
}

func (l *TestLanguageBuilder) Base() string {
func (l *TestLanguageBuilder) Base(customImage string) string {
l.BaseInvoked = true
return l.BaseFn()
return l.BaseFn(customImage)
}

func (l *TestLanguageBuilder) WriteShared(job buildJob) ([]imageLayer, error) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/oci/go_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import (

type goBuilder struct{}

func (b goBuilder) Base() string {
return "" // scratch
func (b goBuilder) Base(customImage string) string {
// if not defined -> return "", meaning building from scratch
return customImage
}

func (b goBuilder) Configure(_ buildJob, _ v1.Platform, cf v1.ConfigFile) (v1.ConfigFile, error) {
Expand Down
5 changes: 4 additions & 1 deletion pkg/oci/python_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ var defaultPythonBase = "python:3.13-slim" // Moving from docker.io. See issue

type pythonBuilder struct{}

func (b pythonBuilder) Base() string {
func (b pythonBuilder) Base(customBase string) string {
if customBase != "" {
return customBase
}
return defaultPythonBase
}

Expand Down
Loading
Loading