feat!: deploy autobuild and flag persistence (#1079)

* feat: deploy autobuild and flag persistence

* cleanup

* help text, comments and test cleanup

* removing usurped commands.txt

* add platform to deploy synopsis help
This commit is contained in:
Luke Kingland 2022-09-14 01:45:10 -10:00 committed by GitHub
parent 966a150c58
commit 15713b2a75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1303 additions and 1028 deletions

View File

@ -32,9 +32,6 @@ const (
// XDG_CONFIG_HOME set, and no WithConfigPath was used.
DefaultConfigPath = ".config/func"
// DefaultBuildType is the default build type for a function
DefaultBuildType = BuildTypeLocal
// RunDataDir holds transient runtime metadata
// By default it is excluded from source control.
RunDataDir = ".func"
@ -528,7 +525,7 @@ func (c *Client) Create(cfg Function) (err error) {
return err
}
if hasFunc {
return fmt.Errorf("Function at '%v' already initialized", cfg.Root)
return fmt.Errorf("function at '%v' already initialized", cfg.Root)
}
// Path is defaulted to the current working directory
@ -727,20 +724,15 @@ func (c *Client) Deploy(ctx context.Context, path string) (err error) {
return err
}
// RunPipeline runs a Pipeline to Build and deploy the function at path.
// In a parameter accepts git configuration options that we don't want to persist into func.yaml.
func (c *Client) RunPipeline(ctx context.Context, path string, git Git) (err error) {
// RunPipeline runs a Pipeline to build and deploy the function.
// Returned function contains applicable registry and deployed image name.
func (c *Client) RunPipeline(ctx context.Context, f Function) (Function, error) {
var err error
go func() {
<-ctx.Done()
c.progressListener.Stopping()
}()
f, err := NewFunction(path)
if err != nil {
err = fmt.Errorf("failed to laod function: %w", err)
return
}
// Default function registry to the client's global registry
if f.Registry == "" {
f.Registry = c.registry
@ -748,31 +740,18 @@ func (c *Client) RunPipeline(ctx context.Context, path string, git Git) (err err
// If no image name has been yet defined (not yet built/deployed), calculate.
// Image name is stored on the function for later use by deploy, etc.
// TODO: write this to .func/build instead, and populate f.Image on deploy
// such that local builds do not dirty the work tree.
if f.Image == "" {
if f.Image, err = f.ImageName(); err != nil {
return
}
//Write (save) - Serialize the function to disk
//Will now contain populated image name.
if err = f.Write(); err != nil {
return
return f, err
}
}
// Git configuration options specified as arguments don't get saved to func.yaml,
// but are used in the pipeline invocation
f.Git = git
// Build and deploy function using Pipeline
err = c.pipelinesProvider.Run(ctx, f)
if err != nil {
err = fmt.Errorf("failed to run pipeline: %w", err)
return
if err := c.pipelinesProvider.Run(ctx, f); err != nil {
return f, fmt.Errorf("failed to run pipeline: %w", err)
}
return err
return f, nil
}
func (c *Client) Route(path string) (err error) {
@ -866,7 +845,7 @@ func (c *Client) Remove(ctx context.Context, cfg Function, deleteAll bool) error
return err
}
if !f.Initialized() {
return fmt.Errorf("Function at %v can not be removed unless initialized. Try removing by name", f.Root)
return fmt.Errorf("function at %v can not be removed unless initialized. Try removing by name", f.Root)
}
functionName = f.Name
cfg = f

View File

@ -580,7 +580,7 @@ func TestClient_Run_DataDir(t *testing.T) {
t.Errorf(".gitignore does not include '/%v' ignore directive", fn.RunDataDir)
}
// TestClient_Update ensures that the deployer properly invokes the build/push/deploy
// TestClient_Update ensures that updating invokes the build/push/deploy
// process, erroring if run on a directory uncreated.
func TestClient_Update(t *testing.T) {
var (
@ -749,6 +749,7 @@ func TestClient_Deploy_RegistryUpdate(t *testing.T) {
}
if f.Image != expected { // DOES change to bob
t.Errorf("expected image name to stay '%v' and not be updated, but got '%v'", expected, f.Image)
}
}
@ -1068,17 +1069,19 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
fn.WithPipelinesProvider(mock.NewPipelinesProvider()),
fn.WithRegistry("example.com/alice"))
repoUrl := "http://example-git.com/alice/myfunc.gi"
git := fn.Git{
URL: &repoUrl,
f := fn.Function{
Name: "myfunc",
Runtime: "node",
Root: root,
Git: fn.Git{URL: "http://example-git.com/alice/myfunc.git"},
}
err := client.Create(fn.Function{Name: "myfunc", Runtime: "node", Root: root})
err := client.Create(f)
if err != nil {
t.Fatal(err)
}
f, err := fn.NewFunction(root)
f, err = fn.NewFunction(root)
if err != nil {
t.Fatal(err)
}
@ -1089,11 +1092,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
}
// Upon pipeline run, the function should be populated;
if err = client.RunPipeline(context.Background(), root, git); err != nil {
t.Fatal(err)
}
f, err = fn.NewFunction(root)
if err != nil {
if f, err = client.RunPipeline(context.Background(), f); err != nil {
t.Fatal(err)
}
expected := "example.com/alice/myfunc:latest"
@ -1101,7 +1100,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
t.Fatalf("expected image '%v', got '%v'", expected, f.Image)
}
expected = "example.com/alice"
if f.Registry != "example.com/alice" {
if f.Registry != expected {
t.Fatalf("expected registry '%v', got '%v'", expected, f.Registry)
}
@ -1111,7 +1110,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
t.Fatal(err)
}
// Upon pipeline run, the function should be populated;
if err = client.RunPipeline(context.Background(), root, git); err != nil {
if f, err = client.RunPipeline(context.Background(), f); err != nil {
t.Fatal(err)
}
expected = "registry2.example.com/bob/myfunc:latest"
@ -1119,7 +1118,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
t.Fatalf("expected image '%v', got '%v'", expected, f.Image)
}
expected = "example.com/alice"
if f.Registry != "example.com/alice" {
if f.Registry != expected {
// Note that according to current logic, the function's defined registry
// may be inaccurate. Consider an initial deploy to registryA, followed by
// an explicit mutaiton of the function's .Image member.
@ -1131,9 +1130,9 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
}
}
// TestClient_Deploy_UnbuiltErrors ensures that a call to deploy a function which was not
// fully created (ie. was only initialized, not actually built and deploys)
// yields an expected, and informative, error.
// TestClient_Deploy_UnbuiltErrors ensures that a call to deploy a function
// which was not fully created (ie. was only initialized, not actually built
// or deployed) yields the expected error.
func TestClient_Deploy_UnbuiltErrors(t *testing.T) {
root := "testdata/example.com/testDeployUnbuilt" // Root from which to run the test
defer Using(t, root)()

View File

@ -54,12 +54,13 @@ and the image name is stored in the configuration file.
cmd.Flags().StringP("builder-image", "", "", "builder image, either an as a an image name or a mapping name.\nSpecified value is stored in func.yaml (as 'builder' field) for subsequent builds. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)")
cmd.Flags().StringP("image", "i", "", "Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry (Env: $FUNC_IMAGE)")
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined (Env: $FUNC_REGISTRY)")
cmd.Flags().BoolP("push", "u", false, "Attempt to push the function image after being successfully built")
cmd.Flags().Lookup("push").NoOptDefVal = "true" // --push == --push=true
cmd.Flags().StringP("platform", "", "", "Target platform to build (e.g. linux/amd64).")
setPathFlag(cmd)
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuildersList); err != nil {
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuilderList); err != nil {
fmt.Println("internal: error while calling RegisterFlagCompletionFunc: ", err)
}
@ -77,11 +78,17 @@ and the image name is stored in the configuration file.
}
func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error) {
// Populate a command config from environment variables, flags and potentially
// interactive user prompts if in confirm mode.
config, err := newBuildConfig().Prompt()
if err != nil {
if errors.Is(err, terminal.InterruptErr) {
return nil
}
}
// Validate the config
if err = config.Validate(); err != nil {
return
}
@ -112,35 +119,33 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
if config.Image != "" {
f.Image = config.Image
}
if config.Builder != "" {
f.Builder = config.Builder
}
if config.BuilderImage != "" {
f.BuilderImages[config.Builder] = config.BuilderImage
}
// Validate that a builder short-name was obtained, whether that be from
// the function's prior state, or the value of flags/environment.
if err = ValidateBuilder(f.Builder); err != nil {
return
}
// Choose a builder based on the value of the --builder flag
var builder fn.Builder
if f.Builder == "" || cmd.Flags().Changed("builder") {
f.Builder = config.Builder
} else {
config.Builder = f.Builder
}
if err = ValidateBuilder(config.Builder); err != nil {
return err
}
if config.Builder == builders.Pack {
if config.Platform != "" {
err = fmt.Errorf("the --platform flag works only with s2i build")
return
}
if f.Builder == builders.Pack {
builder = buildpacks.NewBuilder(
buildpacks.WithName(builders.Pack),
buildpacks.WithVerbose(config.Verbose))
} else if config.Builder == builders.S2I {
} else if f.Builder == builders.S2I {
builder = s2i.NewBuilder(
s2i.WithName(builders.S2I),
s2i.WithVerbose(config.Verbose),
s2i.WithPlatform(config.Platform))
}
// Use the user-provided builder image, if supplied
if config.BuilderImage != "" {
f.BuilderImages[config.Builder] = config.BuilderImage
s2i.WithPlatform(config.Platform),
s2i.WithVerbose(config.Verbose))
} else {
err = fmt.Errorf("builder '%v' is not recognized", f.Builder)
return
}
client, done := newClient(ClientConfig{Verbose: config.Verbose},
@ -152,7 +157,19 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
if client.Registry() == "" && f.Registry == "" && f.Image == "" {
// It is not necessary that we validate here, since the client API has
// its own validation, but it does give us the opportunity to show a very
// cli-specific and detailed error message.
// cli-specific and detailed error message and (at least for now) default
// to an interactive prompt.
if interactiveTerminal() {
fmt.Println("A registry for function images is required. For example, 'docker.io/tigerteam'.")
if err = survey.AskOne(
&survey.Input{Message: "Registry for function images:"},
&config.Registry, survey.WithValidator(
NewRegistryValidator(config.Path))); err != nil {
return ErrRegistryRequired
}
fmt.Println("Note: building a function the first time will take longer than subsequent builds")
}
return ErrRegistryRequired
}
@ -271,3 +288,14 @@ func (c buildConfig) Prompt() (buildConfig, error) {
err = survey.Ask(qs, &c)
return c, err
}
// Validate the config passes an initial consistency check
func (c buildConfig) Validate() (err error) {
if c.Platform != "" && c.Builder != builders.S2I {
err = errors.New("Only S2I builds currently support specifying platform")
return
}
return
}

View File

@ -1,8 +1,7 @@
package cmd
import (
"fmt"
"os"
"errors"
"testing"
fn "knative.dev/kn-plugin-func"
@ -20,11 +19,10 @@ func TestBuild_ImageFlag(t *testing.T) {
root, cleanup := Mktemp(t)
defer cleanup()
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root, Registry: TestRegistry}); err != nil {
t.Fatal(err)
}
// Create build command that will use a mock builder.
cmd := NewBuildCmd(NewClientFactory(func() *fn.Client {
return fn.New(fn.WithBuilder(builder))
}))
@ -33,7 +31,7 @@ func TestBuild_ImageFlag(t *testing.T) {
cmd.SetArgs(args)
err := cmd.Execute()
if err != nil {
t.Fatal("Expected error")
t.Fatal(err)
}
// Now load the function and ensure that the image is set correctly.
@ -46,16 +44,35 @@ func TestBuild_ImageFlag(t *testing.T) {
}
}
// TestBuild_RegistryOrImageRequired ensures that when no registry or image are
// provided, and the client has not been instantiated with a default registry,
// an ErrRegistryRequired is received.
// TestDeploy_RegistryOrImageRequired ensures that when no registry or image are
// provided (or exist on the function already), and the client has not been
// instantiated with a default registry, an ErrRegistryRequired is received.
func TestBuild_RegistryOrImageRequired(t *testing.T) {
testRegistryOrImageRequired(NewBuildCmd, t)
}
t.Helper()
root, rm := Mktemp(t)
defer rm()
// TestBuild_ImageAndRegistry
func TestBuild_ImageAndRegistry(t *testing.T) {
testRegistryOrImageRequired(NewBuildCmd, t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
cmd := NewBuildCmd(NewClientFactory(func() *fn.Client {
return fn.New()
}))
cmd.SetArgs([]string{}) // this explicit clearing of args may not be necessary
if err := cmd.Execute(); err != nil {
if !errors.Is(err, ErrRegistryRequired) {
t.Fatalf("expected ErrRegistryRequired, got error: %v", err)
}
}
// earlire test covers the --registry only case, test here that --image
// also succeeds.
cmd.SetArgs([]string{"--image=example.com/alice/myfunc"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
}
// TestBuild_InvalidRegistry ensures that providing an invalid resitry
@ -83,101 +100,61 @@ func TestBuild_BuilderValidated(t *testing.T) {
testBuilderValidated(NewBuildCmd, t)
}
func TestBuild_runBuild(t *testing.T) {
tests := []struct {
name string
pushFlag bool
fileContents string
shouldBuild bool
shouldPush bool
wantErr bool
}{
{
name: "push flag triggers push after build",
pushFlag: true,
fileContents: `name: test-func
runtime: go
created: 2009-11-10 23:00:00`,
shouldBuild: true,
shouldPush: true,
},
{
name: "do not push when --push=false",
pushFlag: false,
fileContents: `name: test-func
runtime: go
created: 2009-11-10 23:00:00`,
shouldBuild: true,
shouldPush: false,
},
{
name: "push flag with failing push",
pushFlag: true,
fileContents: `name: test-func
runtime: go
created: 2009-11-10 23:00:00`,
shouldBuild: true,
shouldPush: true,
wantErr: true,
},
// TestBuild_Push ensures that the build command properly pushes and respects
// the --push flag.
// - Push triggered after a successful build
// - Push not triggered after an unsuccessful build
// - Push can be disabled
func TestBuild_Push(t *testing.T) {
root, rm := Mktemp(t)
defer rm()
f := fn.Function{
Root: root,
Name: "myfunc",
Runtime: "go",
Registry: "example.com/alice",
}
if err := fn.New().Create(f); err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockPusher := mock.NewPusher()
failPusher := &mock.Pusher{
PushFn: func(f fn.Function) (string, error) {
return "", fmt.Errorf("push failed")
},
}
mockBuilder := mock.NewBuilder()
cmd := NewBuildCmd(NewClientFactory(func() *fn.Client {
pusher := mockPusher
if tt.wantErr {
pusher = failPusher
}
return fn.New(
fn.WithBuilder(mockBuilder),
fn.WithPusher(pusher),
)
}))
tempDir, err := os.MkdirTemp("", "func-tests")
if err != nil {
t.Fatalf("temp dir couldn't be created %v", err)
}
t.Log("tempDir created:", tempDir)
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
var (
builder = mock.NewBuilder()
pusher = mock.NewPusher()
cmd = NewBuildCmd(NewClientFactory(func() *fn.Client {
return fn.New(fn.WithRegistry(TestRegistry), fn.WithBuilder(builder), fn.WithPusher(pusher))
}))
)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
fullPath := tempDir + "/func.yaml"
tempFile, err := os.Create(fullPath)
if err != nil {
t.Fatalf("temp file couldn't be created %v", err)
}
_, err = tempFile.WriteString(tt.fileContents)
if err != nil {
t.Fatalf("file content was not written %v", err)
}
// Assert there was no push
if pusher.PushInvoked {
t.Fatal("push should not be invoked by default")
}
cmd.SetArgs([]string{
"--path=" + tempDir,
fmt.Sprintf("--push=%t", tt.pushFlag),
"--registry=docker.io/tigerteam",
})
// Execute with push enabled
cmd.SetArgs([]string{"--push"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
err = cmd.Execute()
if tt.wantErr != (err != nil) {
t.Errorf("Wanted error %v but actually got %v", tt.wantErr, err)
}
// Assert there was a push
if !pusher.PushInvoked {
t.Fatal("push should be invoked when requested and a successful build")
}
if mockBuilder.BuildInvoked != tt.shouldBuild {
t.Errorf("Build execution expected: %v but was actually %v", tt.shouldBuild, mockBuilder.BuildInvoked)
}
// Exeute with push enabled but with a failed build
builder.BuildFn = func(f fn.Function) error {
return errors.New("mock error")
}
pusher.PushInvoked = false
_ = cmd.Execute() // expected error
if tt.shouldPush != (mockPusher.PushInvoked || failPusher.PushInvoked) {
t.Errorf("Push execution expected: %v but was actually mockPusher invoked: %v failPusher invoked %v", tt.shouldPush, mockPusher.PushInvoked, failPusher.PushInvoked)
}
})
// Assert push was not invoked when the build failed
if pusher.PushInvoked {
t.Fatal("push should not be invoked on a failed build")
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/spf13/cobra"
fn "knative.dev/kn-plugin-func"
"knative.dev/kn-plugin-func/builders"
"knative.dev/kn-plugin-func/knative"
)
@ -130,12 +129,20 @@ func CompleteBuilderImageList(cmd *cobra.Command, args []string, complete string
return
}
func CompleteDeployBuildType(cmd *cobra.Command, args []string, complete string) (buildTypes []string, directive cobra.ShellCompDirective) {
buildTypes = fn.AllBuildTypes()
directive = cobra.ShellCompDirectiveDefault
func CompleteBuilderList(cmd *cobra.Command, args []string, complete string) (matches []string, d cobra.ShellCompDirective) {
d = cobra.ShellCompDirectiveNoFileComp
matches = []string{}
if len(complete) == 0 {
matches = KnownBuilders()
return
}
for _, b := range KnownBuilders() {
if strings.HasPrefix(b, complete) {
matches = append(matches, b)
}
}
return
}
func CompleteBuildersList(cmd *cobra.Command, args []string, complete string) ([]string, cobra.ShellCompDirective) {
return builders.All(), cobra.ShellCompDirectiveNoFileComp
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"golang.org/x/term"
@ -28,30 +29,79 @@ import (
func NewDeployCmd(newClient ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy a function",
Long: `Deploy a function
Short: "Deploy a Function",
Long: `
NAME
{{.Name}} deploy - Deploy a Function
Builds a container image for the function and deploys it to the connected Knative enabled cluster.
The function is picked up from the project in the current directory or from the path provided
with --path.
If not already configured, either --registry or --image has to be provided and is then stored
in the configuration file.
SYNOPSIS
{{.Name}} deploy [-R|--remote] [-r|--registry] [-i|--image] [-n|--namespace]
[-e|env] [-g|--git-url] [-t|git-branch] [-d|--git-dir]
[-b|--build] [--builder] [--builder-image] [-p|--push]
[--platform] [-c|--confirm] [-v|--verbose]
If the function is already deployed, it is updated with a new container image
that is pushed to an image registry, and finally the function's Knative service is updated.
`,
Example: `
# Build and deploy the function from the current directory's project. The image will be
# pushed to "quay.io/myuser/<function name>" and deployed as Knative service with the
# same name as the function to the currently connected cluster.
{{.Name}} deploy --registry quay.io/myuser
DESCRIPTION
Deploys a function to the currently configured Knative-enabled cluster.
By default the function in the current working directory is deployed, or at
the path defined by --path.
A function which was previously deployed will be updated when re-deployed.
The function is built into a container for transport to the destination
cluster by way of a registry. Therefore --registry must be provided or have
previously been configured for the function. This registry is also used to
determine the final built image tag for the function. This final image name
can be provided explicitly using --image, in which case it is used in place
of --registry.
To run deploy using an interactive mode, use the --confirm (-c) option.
This mode is useful for the first deployment in particular, since subsdequent
deployments remember most of the settings provided.
Building
By default the function will be built if it has not yet been built, or if
changes are detected in the function's source. The --build flag can be
used to override this behavior and force building either on or off.
Remote
Building and pushing (deploying) is by default run on localhost. This
process can also be triggered to run remotely in a Tekton-enabled cluster.
The --remote flag indicates that a build and deploy pipeline should be
invoked in the remote. Functions deployed in this manner must have their
source code kept in a git repository, and the URL to this source provided
via --git-url. A specific branch can be specified with --git-branch.
EXAMPLES
o Deploy the function using interactive prompts. This is useful for the first
deployment, since most settings will be remembered for future deployments.
$ {{.Name}} deploy -c
o Deploy the function in the current working directory.
The function image will be pushed to "ghcr.io/alice/<Function Name>"
$ {{.Name}} deploy --registry ghcr.io/alice
o Deploy the function in the current working directory, manually specifying
the final image name and target cluster namespace.
$ {{.Name}} deploy --image ghcr.io/alice/myfunc --namespace myns
o Trigger a remote deploy, which instructs the cluster to build and deploy
the function in the specified git repository.
$ {{.Name}} deploy --remote --git-url=https://example.com/alice/myfunc.git
o Deploy the function, rebuilding the image even if no changes have been
detected in the local filesystem (source).
$ {{.Name}} deploy --build
o Deploy without rebuilding, even if changes have been detected in the
local filesystem.
$ {{.Name}} deploy --build=false
# Same as above but using a full image name, that will create a Knative service "myfunc" in
# the namespace "myns"
{{.Name}} deploy --image quay.io/myuser/myfunc -n myns
`,
SuggestFor: []string{"delpoy", "deplyo"},
PreRunE: bindEnv("image", "path", "registry", "confirm", "build", "push", "git-url", "git-branch", "git-dir", "builder", "builder-image", "platform"),
PreRunE: bindEnv("confirm", "env", "git-url", "git-branch", "git-dir", "remote", "build", "builder", "builder-image", "image", "registry", "push", "platform", "path", "namespace"),
}
cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)")
@ -61,21 +111,21 @@ that is pushed to an image registry, and finally the function's Knative service
cmd.Flags().StringP("git-url", "g", "", "Repo url to push the code to be built (Env: $FUNC_GIT_URL)")
cmd.Flags().StringP("git-branch", "t", "", "Git branch to be used for remote builds (Env: $FUNC_GIT_BRANCH)")
cmd.Flags().StringP("git-dir", "d", "", "Directory in the repo where the function is located (Env: $FUNC_GIT_DIR)")
cmd.Flags().StringP("build", "", fn.DefaultBuildType, fmt.Sprintf("Build specifies the way the function should be built. Supported types are %s (Env: $FUNC_BUILD)", fn.SupportedBuildTypes(true)))
// Flags shared with Build specifically related to building:
cmd.Flags().StringP("builder", "b", builders.Default, fmt.Sprintf("build strategy to use when creating the underlying image. Currently supported build strategies are %s.", KnownBuilders()))
cmd.Flags().StringP("builder-image", "", "", "builder image, either an as a an image name or a mapping name.\nSpecified value is stored in func.yaml (as 'builder' field) for subsequent builds. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().BoolP("remote", "", false, "Trigger a remote deployment. Default is to deploy and build from the local system: $FUNC_REMOTE)")
// Flags shared with Build (specifically related to the build step):
cmd.Flags().StringP("build", "", "auto", "Build the function. [auto|true|false]. [Env: $FUNC_BUILD]")
cmd.Flags().Lookup("build").NoOptDefVal = "true" // --build is equivalient to --build=true
cmd.Flags().StringP("builder", "b", builders.Default, fmt.Sprintf("builder to use when creating the underlying image. Currently supported builders are %s.", KnownBuilders()))
cmd.Flags().StringP("builder-image", "", "", "The image the specified builder should use; either an as an image name or a mapping. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().StringP("image", "i", "", "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. (Env: $FUNC_IMAGE)")
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
cmd.Flags().BoolP("push", "u", true, "Attempt to push the function image to registry before deploying (Env: $FUNC_PUSH)")
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'ghcr.io/myuser'. The full image name is automatically determined. (Env: $FUNC_REGISTRY)")
cmd.Flags().BoolP("push", "u", true, "Push the function image to registry before deploying (Env: $FUNC_PUSH)")
cmd.Flags().StringP("platform", "", "", "Target platform to build (e.g. linux/amd64).")
cmd.Flags().StringP("namespace", "n", "", "Deploy into a specific namespace. (Env: $FUNC_NAMESPACE)")
setPathFlag(cmd)
if err := cmd.RegisterFlagCompletionFunc("build", CompleteDeployBuildType); err != nil {
fmt.Println("internal: error while calling RegisterFlagCompletionFunc: ", err)
}
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuildersList); err != nil {
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuilderList); err != nil {
fmt.Println("internal: error while calling RegisterFlagCompletionFunc: ", err)
}
@ -92,12 +142,17 @@ that is pushed to an image registry, and finally the function's Knative service
return cmd
}
// runDeploy gathers configuration from environment, flags and the user,
// merges these into the function requested, and triggers either a remote or
// local build-and-deploy.
func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error) {
// Create a deploy config from environment variables and flags
config, err := newDeployConfig(cmd)
if err != nil {
return
}
// Prompt the user to potentially change config interactively.
config, err = config.Prompt()
if err != nil {
if err == terminal.InterruptErr {
@ -106,17 +161,9 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
return
}
//if --image contains '@', validate image digest and disable build and push if not set, otherwise return an error
// TODO(lkingland): this logic could be assimilated into the Deploy Config
// struct and it's constructor.
imageSplit := strings.Split(config.Image, "@")
imageDigestProvided := false
if len(imageSplit) == 2 {
if config, err = parseImageDigest(imageSplit, config, cmd); err != nil {
return
}
imageDigestProvided = true
// Validate the config
if err = config.Validate(); err != nil {
return
}
// Load the function, and if it exists (path initialized as a function), merge
@ -139,57 +186,62 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
if config.Image != "" {
f.Image = config.Image
}
if config.ImageDigest != "" {
fmt.Fprintf(cmd.OutOrStdout(), "Deploying image '%v' with digest '%s'. Build and push are disabled.\n", f.Image, f.ImageDigest)
f.ImageDigest = config.ImageDigest
}
if config.Builder != "" {
f.Builder = config.Builder
}
if config.BuilderImage != "" {
f.BuilderImages[config.Builder] = config.BuilderImage
}
if config.GitURL != "" {
parts := strings.Split(config.GitURL, "#")
f.Git.URL = parts[0]
if len(parts) == 2 { // see Validatee() where len enforced to be <= 2
f.Git.Revision = parts[1]
}
}
if config.GitBranch != "" {
f.Git.Revision = config.GitBranch
}
if config.GitDir != "" {
f.Git.ContextDir = config.GitDir
}
f.Namespace, err = checkNamespaceDeploy(f.Namespace, config.Namespace)
f.Namespace = namespace(config, f, cmd.ErrOrStderr())
if err != nil {
return
}
f.Envs, _, err = mergeEnvs(f.Envs, config.EnvToUpdate, config.EnvToRemove)
if err != nil {
return
}
currentBuildType := config.BuildType
// if build type has been explicitly set as flag, validate it and override function config
if config.BuildType != "" {
err = validateBuildType(config.BuildType)
if err != nil {
return err
}
} else {
currentBuildType = f.BuildType
// Validate that a builder short-name was obtained, whether that be from
// the function's prior state, or the value of flags/environment.
if err = ValidateBuilder(f.Builder); err != nil {
return
}
if imageDigestProvided {
// TODO(lkingland): This could instead be part of the config, relying on
// zero values rather than a flag indicating "Image Digest was Provided"
f.ImageDigest = imageSplit[1] // save image digest if provided in --image
}
// Choose a builder based on the value of the --builder flag
// Choose a builder based on the value of the --builder flag and a possible
// override for the build image for that builder to use from the optional
// builder-image flag.
var builder fn.Builder
if f.Builder == "" || cmd.Flags().Changed("builder") {
f.Builder = config.Builder
if f.Builder == builders.Pack {
builder = buildpacks.NewBuilder(
buildpacks.WithName(builders.Pack),
buildpacks.WithVerbose(config.Verbose))
} else if f.Builder == builders.S2I {
builder = s2i.NewBuilder(
s2i.WithName(builders.S2I),
s2i.WithPlatform(config.Platform),
s2i.WithVerbose(config.Verbose))
} else {
config.Builder = f.Builder
}
if err = ValidateBuilder(config.Builder); err != nil {
return err
}
if config.Builder == builders.Pack {
if config.Platform != "" {
err = fmt.Errorf("the --platform flag works only with s2i build")
return
}
builder = buildpacks.NewBuilder(buildpacks.WithVerbose(config.Verbose))
} else if config.Builder == builders.S2I {
builder = s2i.NewBuilder(s2i.WithVerbose(config.Verbose), s2i.WithPlatform(config.Platform))
}
// Use the user-provided builder image, if supplied
if config.BuilderImage != "" {
f.BuilderImages[config.Builder] = config.BuilderImage
err = fmt.Errorf("builder '%v' is not recognized", f.Builder)
return
}
client, done := newClient(ClientConfig{Namespace: f.Namespace, Verbose: config.Verbose},
@ -199,58 +251,95 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
// Default Client Registry, Function Registry or explicit Image required
if client.Registry() == "" && f.Registry == "" && f.Image == "" {
if interactiveTerminal() {
// to be consistent, this should throw an error, with the registry
// prompting code placed within config.Prompt and triggered with --confirm
fmt.Println("A registry for function images is required. For example, 'docker.io/tigerteam'.")
if err = survey.AskOne(
&survey.Input{Message: "Registry for function images:"},
&config.Registry, survey.WithValidator(
NewRegistryValidator(config.Path))); err != nil {
return ErrRegistryRequired
}
fmt.Println("Note: building a function the first time will take longer than subsequent builds")
}
return ErrRegistryRequired
}
// NOTE: curently need to preemptively write out function state until
// the API is updated to use instances, at which point we will only
// write on success.
if err = f.Write(); err != nil {
return
}
switch currentBuildType {
case fn.BuildTypeLocal, "":
if config.GitURL != "" || config.GitDir != "" || config.GitBranch != "" {
return fmt.Errorf("remote git arguments require the --build=git flag")
// Perform the deployment either remote or local.
if config.Remote {
if f.Git.URL == "" {
return ErrURLRequired // Provides CLI-specific help text
}
if err := client.Build(cmd.Context(), config.Path); err != nil {
return err
// Invoke a remote build/push/deploy pipeline
// Returned is the function with fields like Registry and Image populated.
if f, err = client.RunPipeline(cmd.Context(), f); err != nil {
return
}
case fn.BuildTypeGit:
git := f.Git
if config.GitURL != "" {
git.URL = &config.GitURL
if strings.Contains(config.GitURL, "#") {
parts := strings.Split(config.GitURL, "#")
git.URL = &parts[0]
git.Revision = &parts[1]
} else {
if err = f.Write(); err != nil { // TODO: remove when client API uses 'f'
return
}
if build(config.Build, f, client) { // --build or "auto" with FS changes
if err = client.Build(cmd.Context(), f.Root); err != nil {
return
}
}
if config.GitBranch != "" {
git.Revision = &config.GitBranch
if f, err = fn.NewFunction(f.Root); err != nil { // TODO: remove when client API uses 'f'
return
}
if config.GitDir != "" {
git.ContextDir = &config.GitDir
if config.Push {
if err = client.Push(cmd.Context(), f.Root); err != nil {
return
}
}
if err = client.Deploy(cmd.Context(), f.Root); err != nil {
return
}
if f, err = fn.NewFunction(f.Root); err != nil { // TODO: remove when client API uses 'f'
return
}
return client.RunPipeline(cmd.Context(), config.Path, git)
case fn.BuildTypeDisabled:
// nothing needed to be done for `build=disabled`
default:
return ErrInvalidBuildType(fmt.Errorf("unknown build type: %s", currentBuildType))
}
if config.Push {
if err := client.Push(cmd.Context(), config.Path); err != nil {
// mutations persisted on success
return f.Write()
}
// build returns true if the value of buildStr is a truthy value, or if
// it is the literal "auto" and the function reports as being currently
// unbuilt. Invalid errors are not reported as this is the purview of
// deployConfig.Validate
func build(buildCfg string, f fn.Function, client *fn.Client) bool {
if buildCfg == "auto" {
return !client.Built(f.Root) // first build or modified filesystem
}
build, _ := strconv.ParseBool(buildCfg)
return build
}
func NewRegistryValidator(path string) survey.Validator {
return func(val interface{}) error {
// if the value passed in is the zero value of the appropriate type
if len(val.(string)) == 0 {
return ErrRegistryRequired
}
f, err := fn.NewFunction(path)
if err != nil {
return err
}
}
return client.Deploy(cmd.Context(), config.Path)
// Set the function's registry to that provided
f.Registry = val.(string)
_, err = f.ImageName() //image can be derived without any error
if err != nil {
return fmt.Errorf("invalid registry [%q]: %w", val.(string), err)
}
return nil
}
}
// ValidateBuilder ensures that the given builder is one that the CLI
@ -396,6 +485,15 @@ you can install docker credential helper https://github.com/docker/docker-creden
type deployConfig struct {
buildConfig
// Perform build using the settings from the embedded buildConfig struct.
// Acceptable values are the keyword 'auto', or a truthy value such as
// 'true', 'false, '1' or '0'.
Build string
// Remote indicates the deployment (and possibly build) process are to
// be triggered in a remote environment rather than run locally.
Remote bool
// Namespace override for the deployed function. If provided, the
// underlying platform will be instructed to deploy the function to the given
// namespace (if such a setting is applicable; such as for Kubernetes
@ -404,9 +502,6 @@ type deployConfig struct {
// (~/.kube/config) in the case of Kubernetes.
Namespace string
// Build the associated function before deploying.
BuildType string
// Envs passed via cmd to be added/updated
EnvToUpdate *util.OrderedMap
@ -421,6 +516,9 @@ type deployConfig struct {
// Directory in the git repo where the function is located
GitDir string
// ImageDigest is automatically split off an --image tag
ImageDigest string
}
// newDeployConfig creates a buildConfig populated from command flags and
@ -431,23 +529,24 @@ func newDeployConfig(cmd *cobra.Command) (deployConfig, error) {
return deployConfig{}, err
}
// We need to know whether the `build`` flag had been explicitly set,
// to distinguish between unset and default value.
var buildType string
if viper.IsSet("build") {
buildType = viper.GetString("build")
}
return deployConfig{
c := deployConfig{
buildConfig: newBuildConfig(),
Build: viper.GetString("build"),
Remote: viper.GetBool("remote"),
Namespace: viper.GetString("namespace"),
BuildType: buildType,
EnvToUpdate: envToUpdate,
EnvToRemove: envToRemove,
GitURL: viper.GetString("git-url"),
GitBranch: viper.GetString("git-branch"),
GitDir: viper.GetString("git-dir"),
}, nil
ImageDigest: "", // automatically split off --image if provided below
}
if c.Image, c.ImageDigest, err = parseImage(c.Image); err != nil {
return c, err
}
return c, nil
}
// Prompt the user with value of config members, allowing for interaractive changes.
@ -459,10 +558,17 @@ func (c deployConfig) Prompt() (deployConfig, error) {
}
var qs = []*survey.Question{
{
Name: "namespace",
Prompt: &survey.Input{
Message: "Destination namespace:",
Default: c.Namespace,
},
},
{
Name: "path",
Prompt: &survey.Input{
Message: "Project path:",
Message: "Function source path:",
Default: c.Path,
},
},
@ -502,82 +608,109 @@ func (c deployConfig) Prompt() (deployConfig, error) {
return c, err
}
// ErrInvalidBuildType indicates that the passed build type was invalid.
type ErrInvalidBuildType error
// ValidateBuildType validatest that the input Build type is valid for deploy command
func validateBuildType(buildType string) error {
if errs := fn.ValidateBuildType(buildType, false, true); len(errs) > 0 {
return ErrInvalidBuildType(errors.New(strings.Join(errs, "")))
}
return nil
}
func parseImageDigest(imageSplit []string, config deployConfig, cmd *cobra.Command) (deployConfig, error) {
if !strings.HasPrefix(imageSplit[1], "sha256:") {
return config, fmt.Errorf("value '%s' in --image has invalid prefix syntax for digest (should be 'sha256:')", config.Image)
// Validate the config passes an initial consistency check
func (c deployConfig) Validate() (err error) {
// Bubble validation
if err = c.buildConfig.Validate(); err != nil {
return
}
if len(imageSplit[1][7:]) != 64 {
return config, fmt.Errorf("sha256 hash in '%s' from --image has the wrong length (%d), should be 64", imageSplit[1], len(imageSplit[1][7:]))
// Can not enable build when specifying an --image
truthy := func(s string) bool {
v, _ := strconv.ParseBool(s)
return v
}
if c.ImageDigest != "" && truthy(c.Build) {
return errors.New("building can not be enabled when using an image with digest")
}
// if --build was set but not as 'disabled', return an error
if cmd.Flags().Changed("build") && config.BuildType != "disabled" {
return config, fmt.Errorf("the --build flag '%s' is not valid when using --image with digest", config.BuildType)
// Can not push when specifying an --image
if c.ImageDigest != "" && c.Push {
return errors.New("pushing is not valid when specifying an image with digest")
}
// if the --push flag was set by a user to 'true', return an error
if cmd.Flags().Changed("push") && config.Push {
return config, fmt.Errorf("the --push flag '%v' is not valid when using --image with digest", config.Push)
// Git settings are only avaolabe with --remote
if (c.GitURL != "" || c.GitDir != "" || c.GitBranch != "") && !c.Remote {
return errors.New("git settings (--git-url --git-dir and --git-branch) are currently only available when triggering remote deployments (--remote)")
}
fmt.Printf("Deploying existing image with digest %s. Build and push are disabled.\n", imageSplit[1])
// Git URL can contain at maximum one '#'
urlParts := strings.Split(c.GitURL, "#")
if len(urlParts) > 2 {
return fmt.Errorf("invalid --git-url '%v'", c.GitURL)
}
config.BuildType = "disabled"
config.Push = false
config.Image = imageSplit[0]
return config, nil
}
// checkNamespaceDeploy checks current namespace against func.yaml and warns if its different
// or sets namespace to be written in func.yaml if its the first deployment
func checkNamespaceDeploy(funcNamespace string, confNamespace string) (string, error) {
var namespace string
var err error
// Choose target namespace
if confNamespace != "" {
namespace = confNamespace // --namespace takes precidence
} else if funcNamespace != "" {
namespace = funcNamespace // value from previous deployment (func.yaml) 2nd priority
} else {
// Try to derive a default from the current k8s context, if any.
namespace, err = k8s.GetNamespace("")
if err != nil {
return "", nil // configured k8s environment not required
// --build can be "auto"|true|false
if c.Build != "auto" {
if _, err := strconv.ParseBool(c.Build); err != nil {
return fmt.Errorf("unrecognized value for --build '%v'. accepts 'auto', 'true' or 'false' (or similarly truthy value)", c.Build)
}
}
currNamespace, err := k8s.GetNamespace("")
if err == nil && namespace != currNamespace {
fmt.Fprintf(os.Stderr, "Warning: Current namespace '%s' does not match function which is deployed at '%s' namespace\n", currNamespace, funcNamespace)
return
}
func parseImage(v string) (name, digest string, err error) {
vv := strings.Split(v, "@")
if len(vv) < 2 {
name = v
return
}
name = vv[0]
digest = vv[1]
if !strings.HasPrefix(digest, "sha256:") {
return v, "", fmt.Errorf("image '%s' has an invalid prefix syntax for digest (should be 'sha256:')", v)
}
if funcNamespace != "" && namespace != funcNamespace {
fmt.Fprintf(os.Stderr, "Warning: New namespace '%s' does not match current namespace '%s'\n", namespace, funcNamespace)
if len(digest[7:]) != 64 {
return v, "", fmt.Errorf("sha256 hash in '%s' has the wrong length (%d), should be 64", digest, len(digest[7:]))
}
return namespace, nil
return
}
// namespace returns the final namespace to use when considering
// both provided values (flag or environment variables), the
// namespace at which the function is currently deployed, and the
// default of the currently active namespace.
// Warnings are printed to stderr when the combination may be
// confusing to the user.
func namespace(cfg deployConfig, f fn.Function, stderr io.Writer) (namespace string) {
var err error
if cfg.Namespace != "" {
namespace = cfg.Namespace // --namespace takes precidence
} else if f.Namespace != "" {
namespace = f.Namespace // value from previous deployment (func.yaml) 2nd priority
} else {
// Try to derive a default from the current k8s context, if any.
if namespace, err = k8s.GetNamespace(""); err != nil {
fmt.Fprintln(stderr, "Warning: no namespace provided, and none currently active. Continuing to attempt deployment")
}
}
// Warn if in a different namespace than active
active, err := k8s.GetNamespace("")
if err == nil && namespace != active {
fmt.Fprintf(stderr, "Warning: Function is in namespace '%s', but currently active namespace is '%s'. Continuing with redeployment to '%s'.\n", f.Namespace, active, f.Namespace)
}
// Warn if potentially creating an orphan
if f.Namespace != "" && namespace != f.Namespace {
fmt.Fprintf(stderr, "Warning: function is in namespace '%s', but requested namespace is '%s'. Continuing with deployment to '%v'.\n", f.Namespace, namespace, namespace)
}
return
}
var ErrRegistryRequired = errors.New(`A container registry is required. For example:
--registry docker.io/myusername
For more advanced usage, it is also possible to specify the exact image to use. For example:
--image docker.io/myusername/myfunc:latest
To run the command in an interactive mode, use --confirm (-c)`)
var ErrURLRequired = errors.New(`The function is not associated with a Git repository, and needs one in order to perform a remote deployment. For example:
--remote --git-url=https://git.example.com/namespace/myFunction
To run the deploy command in an interactive mode, use --confirm (-c)`)

File diff suppressed because it is too large Load Diff

View File

@ -74,9 +74,9 @@ func runList(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error
// prints bo only on --verbose. Possible future tweak, as I don't want to
// make functional changes during a refactor.
if config.Namespace != "" && !config.AllNamespaces {
fmt.Printf("No functions found in '%v' namespace\n", config.Namespace)
fmt.Printf("no functions found in namespace '%v'\n", config.Namespace)
} else {
fmt.Println("No functions found")
fmt.Println("no functions found")
}
return
}

View File

@ -33,8 +33,8 @@ func Test_newPromptForCredentials(t *testing.T) {
go func() {
chars := expectedCreds.Username + enter + expectedCreds.Password + enter
for _, ch := range chars {
time.Sleep(time.Millisecond * 100)
_, _ = console.Send(string(ch))
time.Sleep(time.Millisecond * 50)
}
}()

View File

@ -70,8 +70,6 @@ func runRun(cmd *cobra.Command, args []string, newClient ClientFactory) (err err
return
}
// Load the Function, and if it exists (path initialized as a Function), merge
// in any env vars from the context and save.
function, err := fn.NewFunction(config.Path)
if err != nil {
return
@ -79,7 +77,6 @@ func runRun(cmd *cobra.Command, args []string, newClient ClientFactory) (err err
if !function.Initialized() {
return fmt.Errorf("the given path '%v' does not contain an initialized function", config.Path)
}
var updated int
function.Envs, updated, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
if err != nil {
@ -108,20 +105,20 @@ func runRun(cmd *cobra.Command, args []string, newClient ClientFactory) (err err
return
}
}
fmt.Println("Detected function was already built. Use --build to override this behavior.")
fmt.Println("Function already built. Use --build to force a rebuild.")
// Otherwise, --build should parse to a truthy value which indicates an explicit
// override.
} else {
build, err := strconv.ParseBool(config.Build)
if err != nil {
return fmt.Errorf("invalid value for --build '%v'. accepts 'auto', 'true' or 'false' (or similarly truthy value)", build)
return fmt.Errorf("unrecognized value for --build '%v'. accepts 'auto', 'true' or 'false' (or similarly truthy value)", build)
}
if build {
if err = client.Build(cmd.Context(), config.Path); err != nil {
return err
}
} else {
fmt.Println("Function build disabled. Skipping build.")
fmt.Println("Function build disabled.")
}
}

View File

@ -6,7 +6,7 @@ This guide walks through the process of configuring a kind cluster to run Functi
* kind v0.8.1 - [Install Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
* Kubectl v1.17.3 - [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl)
Follow either the Local Access configuraiton step or the (optional) more lengthy remote configuration section.
Follow either the Local Access configuration step or the (optional) more lengthy remote configuration section.
## Configuring for local access
@ -108,7 +108,7 @@ The nodes should now be able to ping each other using their wireguard-protected
```
wg show
```
For OS X hosts, skip the aforementioned systemd configuraiton, and instead install the Wireguard app from the App store, and then import the following configuration file.
For OS X hosts, skip the aforementioned systemd configuration, and instead install the Wireguard app from the App store, and then import the following configuration file.
```
[Interface]
Address=10.10.10.2/32

View File

@ -4,7 +4,7 @@ Serverless functions
### Synopsis
Serverless functions v0.0.0-source-2022-08-24T12:32:04-04:00
Serverless functions v0.0.0-source-2022-09-12T14:54:32-06:00
Create, build and deploy Knative functions
@ -42,7 +42,7 @@ EXAMPLES
* [func config](func_config.md) - Configure a function
* [func create](func_create.md) - Create a function project
* [func delete](func_delete.md) - Undeploy a function
* [func deploy](func_deploy.md) - Deploy a function
* [func deploy](func_deploy.md) - Deploy a Function
* [func info](func_info.md) - Show details of a function
* [func invoke](func_invoke.md) - Invoke a function
* [func languages](func_languages.md) - List available function language runtimes

View File

@ -1,58 +1,102 @@
## func deploy
Deploy a function
Deploy a Function
### Synopsis
Deploy a function
Builds a container image for the function and deploys it to the connected Knative enabled cluster.
The function is picked up from the project in the current directory or from the path provided
with --path.
If not already configured, either --registry or --image has to be provided and is then stored
in the configuration file.
NAME
func deploy - Deploy a Function
SYNOPSIS
func deploy [-R|--remote] [-r|--registry] [-i|--image] [-n|--namespace]
[-e|env] [-g|--git-url] [-t|git-branch] [-d|--git-dir]
[-b|--build] [--builder] [--builder-image] [-p|--push]
[-c|--confirm] [-v|--verbose]
DESCRIPTION
Deploys a function to the currently configured Knative-enabled cluster.
By default the function in the current working directory is deployed, or at
the path defined by --path.
A function which was previously deployed will be updated when re-deployed.
The function is built into a container for transport to the destination
cluster by way of a registry. Therefore --registry must be provided or have
previously been configured for the function. This registry is also used to
determine the final built image tag for the function. This final image name
can be provided explicitly using --image, in which case it is used in place
of --registry.
To run deploy using an interactive mode, use the --confirm (-c) option.
This mode is useful for the first deployment in particular, since subsdequent
deployments remember most of the settings provided.
Building
By default the function will be built if it has not yet been built, or if
changes are detected in the function's source. The --build flag can be
used to override this behavior and force building either on or off.
Remote
Building and pushing (deploying) is by default run on localhost. This
process can also be triggered to run remotely in a Tekton-enabled cluster.
The --remote flag indicates that a build and deploy pipeline should be
invoked in the remote. Functions deployed in this manner must have their
source code kept in a git repository, and the URL to this source provided
via --git-url. A specific branch can be specified with --git-branch.
EXAMPLES
o Deploy the function using interactive prompts. This is useful for the first
deployment, since most settings will be remembered for future deployments.
$ func deploy -c
o Deploy the function in the current working directory.
The function image will be pushed to "ghcr.io/alice/<Function Name>"
$ func deploy --registry ghcr.io/alice
o Deploy the function in the current working directory, manually specifying
the final image name and target cluster namespace.
$ func deploy --image ghcr.io/alice/myfunc --namespace myns
o Trigger a remote deploy, which instructs the cluster to build and deploy
the function in the specified git repository.
$ func deploy --remote --git-url=https://example.com/alice/myfunc.git
o Deploy the function, rebuilding the image even if no changes have been
detected in the local filesystem (source).
$ func deploy --build
o Deploy without rebuilding, even if changes have been detected in the
local filesystem.
$ func deploy --build=false
If the function is already deployed, it is updated with a new container image
that is pushed to an image registry, and finally the function's Knative service is updated.
```
func deploy
```
### Examples
```
# Build and deploy the function from the current directory's project. The image will be
# pushed to "quay.io/myuser/<function name>" and deployed as Knative service with the
# same name as the function to the currently connected cluster.
func deploy --registry quay.io/myuser
# Same as above but using a full image name, that will create a Knative service "myfunc" in
# the namespace "myns"
func deploy --image quay.io/myuser/myfunc -n myns
```
### Options
```
--build string Build specifies the way the function should be built. Supported types are "disabled", "local" or "git" (Env: $FUNC_BUILD) (default "local")
-b, --builder string build strategy to use when creating the underlying image. Currently supported build strategies are "pack" and "s2i". (default "pack")
--builder-image string builder image, either an as a an image name or a mapping name.
Specified value is stored in func.yaml (as 'builder' field) for subsequent builds. ($FUNC_BUILDER_IMAGE)
-c, --confirm Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)
-e, --env stringArray Environment variable to set in the form NAME=VALUE. You may provide this flag multiple times for setting multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-).
-t, --git-branch string Git branch to be used for remote builds (Env: $FUNC_GIT_BRANCH)
-d, --git-dir string Directory in the repo where the function is located (Env: $FUNC_GIT_DIR)
-g, --git-url string Repo url to push the code to be built (Env: $FUNC_GIT_URL)
-h, --help help for deploy
-i, --image string 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. (Env: $FUNC_IMAGE)
-p, --path string Path to the project directory (Env: $FUNC_PATH) (default ".")
--platform string Target platform to build (e.g. linux/amd64).
-u, --push Attempt to push the function image to registry before deploying (Env: $FUNC_PUSH) (default true)
-r, --registry string Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)
--build string[="true"] Build the function. [auto|true|false]. [Env: $FUNC_BUILD] (default "auto")
-b, --builder string builder to use when creating the underlying image. Currently supported builders are "pack" and "s2i". (default "pack")
--builder-image string The image the specified builder should use; either an as an image name or a mapping. ($FUNC_BUILDER_IMAGE)
-c, --confirm Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)
-e, --env stringArray Environment variable to set in the form NAME=VALUE. You may provide this flag multiple times for setting multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-).
-t, --git-branch string Git branch to be used for remote builds (Env: $FUNC_GIT_BRANCH)
-d, --git-dir string Directory in the repo where the function is located (Env: $FUNC_GIT_DIR)
-g, --git-url string Repo url to push the code to be built (Env: $FUNC_GIT_URL)
-h, --help help for deploy
-i, --image string 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. (Env: $FUNC_IMAGE)
-p, --path string Path to the project directory (Env: $FUNC_PATH) (default ".")
--platform string Target platform to build (e.g. linux/amd64).
-u, --push Push the function image to registry before deploying (Env: $FUNC_PUSH) (default true)
-r, --registry string Registry + namespace part of the image to build, ex 'ghcr.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)
--remote Trigger a remote deployment. Default is to deploy and build from the local system: $FUNC_REMOTE)
```
### Options inherited from parent commands

View File

@ -58,13 +58,8 @@ type Function struct {
// SHA256 hash of the latest image that has been built
ImageDigest string `yaml:"imageDigest"`
// BuildType represents the specified way of building the fuction
// ie. "local" or "git"
BuildType string `yaml:"build" jsonschema:"enum=local,enum=git"`
// Git stores information about remote git repository,
// in case build type "git" is being used
Git Git `yaml:"git"`
// Git stores information about an optionally associated git repository.
Git Git `yaml:"git,omitempty"`
// BuilderImages define optional explicit builder images to use by
// builder implementations in leau of the in-code defaults. They key
@ -145,9 +140,6 @@ func NewFunctionWith(defaults Function) Function {
if defaults.Template == "" {
defaults.Template = DefaultTemplate
}
if defaults.BuildType == "" {
defaults.BuildType = DefaultBuildType
}
return defaults
}
@ -192,12 +184,6 @@ func (f Function) Validate() error {
return errors.New("function root path is required")
}
// if build type == git, we need to check that Git options are specified as well
mandatoryGitOption := false
if f.BuildType == BuildTypeGit {
mandatoryGitOption = true
}
var ctr int
errs := [][]string{
validateVolumes(f.Volumes),
@ -205,8 +191,7 @@ func (f Function) Validate() error {
ValidateEnvs(f.Envs),
validateOptions(f.Options),
ValidateLabels(f.Labels),
ValidateBuildType(f.BuildType, true, false),
validateGit(f.Git, mandatoryGitOption),
validateGit(f.Git),
}
var b strings.Builder

View File

@ -1,55 +0,0 @@
package function
import (
"fmt"
)
const (
BuildTypeDisabled = "disabled" // in general, type "disabled" should be allowed only for "func deploy comamnd" related functionality
BuildTypeLocal = "local"
BuildTypeGit = "git"
//BuildTypeRemote = "remote" // TODO not supported yet
)
func AllBuildTypes() []string {
return []string{BuildTypeLocal, BuildTypeGit, BuildTypeDisabled}
}
// ValidateBuild validates input Build type option from function config.
// If "allowUnset" is set to true, the specified type could be "" -> fallback to DefaultBuildType,
// this option should be used for validating func.yaml file, where users don't have to specify the build type.
// Type "disabled" is allowed only if parameter "allowDisabledBuildType" is set to true,
// in general type "disabled" should be allowed only for "func deploy command" related functionality.
func ValidateBuildType(build string, allowUnset, allowDisabledBuildType bool) (errors []string) {
valid := false
switch build {
case BuildTypeLocal, BuildTypeGit:
valid = true
case BuildTypeDisabled:
if allowDisabledBuildType {
valid = true
}
case "":
if allowUnset {
valid = true
}
}
if !valid {
return []string{fmt.Sprintf("specified build type \"%s\" is not valid, allowed build types are %s", build, SupportedBuildTypes(allowDisabledBuildType))}
}
return
}
// SupportedBuildTypes prints string with supported build types, type "disabled" is
// returned only if parameter "allowDisabledBuildType" is set to true
// in general, type "disabled" should be allowed only for "deploy" related functionality
func SupportedBuildTypes(allowDisabledBuildType bool) string {
msg := ""
if allowDisabledBuildType {
msg = fmt.Sprintf("\"%s\", ", BuildTypeDisabled)
}
return fmt.Sprintf("%s\"%s\" or \"%s\"", msg, BuildTypeLocal, BuildTypeGit)
}

View File

@ -1,74 +0,0 @@
//go:build !integration
// +build !integration
package function
import (
"testing"
)
func Test_validateBuild(t *testing.T) {
tests := []struct {
name string
build string
allowDisabledBuildType bool
allowUnset bool
errs int
}{
{
name: "valid build type - local",
build: "local",
allowDisabledBuildType: true,
allowUnset: false,
errs: 0,
},
{
name: "valid build type - git",
build: "git",
allowDisabledBuildType: true,
allowUnset: false,
errs: 0,
},
{
name: "valid build type - disabled",
build: "disabled",
allowDisabledBuildType: true,
allowUnset: false,
errs: 0,
},
{
name: "build type \"disabled\" is not allowed in this case",
build: "disabled",
allowDisabledBuildType: false,
allowUnset: false,
errs: 1,
},
{
name: "invalid build type",
build: "foo",
allowDisabledBuildType: true,
allowUnset: false,
errs: 1,
},
{
name: "build type not specified - valid option",
allowDisabledBuildType: false,
allowUnset: true,
errs: 0,
},
{
name: "build type not specified - invalid option",
allowDisabledBuildType: false,
allowUnset: false,
errs: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ValidateBuildType(tt.build, tt.allowUnset, tt.allowDisabledBuildType); len(got) != tt.errs {
t.Errorf("validateBuildType() = %v\n got %d errors but want %d", got, len(got), tt.errs)
}
})
}
}

View File

@ -8,21 +8,20 @@ import (
)
type Git struct {
URL *string `yaml:"url,omitempty"`
Revision *string `yaml:"revision,omitempty"`
ContextDir *string `yaml:"contextDir,omitempty"`
URL string `yaml:"url,omitempty"`
Revision string `yaml:"revision,omitempty"`
ContextDir string `yaml:"contextDir,omitempty"`
}
// validateGit validates input Git option from function config
// if mandatoryGit==true, it checks that Git options are specified (ie. build type == git)
func validateGit(git Git, mandatoryGit bool) (errors []string) {
if git.URL != nil {
_, err := giturls.ParseTransport(*git.URL)
// validateGit validates input Git option from Function config
func validateGit(git Git) (errors []string) {
if git.URL != "" {
_, err := giturls.ParseTransport(git.URL)
if err != nil {
_, err = giturls.ParseScp(*git.URL)
_, err = giturls.ParseScp(git.URL)
}
if err != nil {
errMsg := fmt.Sprintf("specified option \"git.url=%s\" is not valid", *git.URL)
errMsg := fmt.Sprintf("specified option \"git.url=%s\" is not valid", git.URL)
originalErr := err.Error()
if !strings.HasSuffix(originalErr, "is not a valid transport") {
@ -30,11 +29,6 @@ func validateGit(git Git, mandatoryGit bool) (errors []string) {
}
errors = append(errors, errMsg)
}
} else {
if mandatoryGit {
errors = append(errors, "\"git.url\" must be specified for this \"build\" type")
}
}
return
}

View File

@ -2,87 +2,71 @@ package function
import (
"testing"
"knative.dev/pkg/ptr"
)
func Test_validateGit(t *testing.T) {
tests := []struct {
name string
git Git
mandatoryGit bool
errs int
name string
git Git
errs int
}{
{
"correct 'Git - only URL https",
Git{
URL: ptr.String("https://myrepo/foo.git"),
URL: "https://myrepo/foo.git",
},
true,
0,
},
{
"correct 'Git - only URL scp",
Git{
URL: ptr.String("git@myrepo:foo.git"),
URL: "git@myrepo:foo.git",
},
true,
0,
},
{
"correct 'Git - URL + revision",
Git{
URL: ptr.String("https://myrepo/foo.git"),
Revision: ptr.String("mybranch"),
URL: "https://myrepo/foo.git",
Revision: "mybranch",
},
true,
0,
},
{
"correct 'Git - URL + context-dir",
Git{
URL: ptr.String("https://myrepo/foo.git"),
ContextDir: ptr.String("my-folder"),
URL: "https://myrepo/foo.git",
ContextDir: "my-folder",
},
true,
0,
},
{
"correct 'Git - URL + revision & context-dir",
Git{
URL: ptr.String("https://myrepo/foo.git"),
Revision: ptr.String("mybranch"),
ContextDir: ptr.String("my-folder"),
URL: "https://myrepo/foo.git",
Revision: "mybranch",
ContextDir: "my-folder",
},
true,
0,
},
{
"incorrect 'Git - bad URL",
Git{
URL: ptr.String("foo"),
URL: "foo",
},
true,
1,
},
{
"incorrect 'Git - missing URL",
Git{},
true,
1,
},
{
"correct 'Git - not mandatory",
Git{},
false,
0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := validateGit(tt.git, tt.mandatoryGit); len(got) != tt.errs {
if got := validateGit(tt.git); len(got) != tt.errs {
t.Errorf("validateGit() = %v\n got %d errors but want %d", got, len(got), tt.errs)
}
})

2
go.mod
View File

@ -44,7 +44,7 @@ require (
k8s.io/api v0.23.5
k8s.io/apimachinery v0.23.5
k8s.io/client-go v1.5.2
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
knative.dev/client v0.31.1
knative.dev/eventing v0.31.3-0.20220802083815-e345f5f3695d
knative.dev/hack v0.0.0-20220629135029-9e09abcd61f0

View File

@ -44,6 +44,18 @@ type Deployer struct {
decorator DeployDecorator
}
// DefaultNamespace attempts to read the kubernetes active namepsace.
// Missing configs or not having an active kuberentes configuration are
// equivalent to having no default namespace (empty string).
func DefaultNamespace() string {
// Get client config, if it exists, and from that the namespace
ns, _, err := k8s.GetClientConfig().Namespace()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: unable to get active namespace: %v\n", err)
}
return ns
}
func NewDeployer(opts ...DeployerOpt) *Deployer {
d := &Deployer{}

View File

@ -4,13 +4,37 @@
package knative
import (
"fmt"
"os"
"testing"
corev1 "k8s.io/api/core/v1"
fn "knative.dev/kn-plugin-func"
. "knative.dev/kn-plugin-func/testing"
)
// Test_DefaultNamespace ensures that if there is an active kubeconfig,
// that namespace will be returned as the default from the public
// DefaultNamespae accessor, empty string otherwise.
func Test_DefaultNamespace(t *testing.T) {
// Update Kubeconfig to indicate the currently active namespace is:
// "test-ns-deploy"
defer WithEnvVar(t, "KUBECONFIG", fmt.Sprintf("%s/testdata/test_default_namespace", cwd()))()
if DefaultNamespace() != "test-ns-deploy" {
t.Fatalf("expected 'test-ns-deploy', got '%v'", DefaultNamespace())
}
}
func cwd() (cwd string) {
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to determine current working directory: %v", err)
os.Exit(1)
}
return cwd
}
func Test_setHealthEndpoints(t *testing.T) {
f := fn.Function{
Name: "testing",

20
knative/testdata/test_default_namespace vendored Normal file
View File

@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://cluster.example.com.com:6443
name: cluster.example.com-com:6443
contexts:
- context:
cluster: cluster.example.com-com:6443
namespace: test-ns-deploy
user: kube:admin/cluster.example.com-com:6443
name: test-ns-deploy/cluster.example.com-com:6443/kube:admin
current-context: test-ns-deploy/cluster.example.com-com:6443/kube:admin
kind: Config
preferences: {}
users:
- name: kubeadmin
user:
token: sha256~XXXXexample-test-hash

View File

@ -26,12 +26,8 @@ func (o OpenshiftMetadataDecorator) UpdateAnnotations(f fn.Function, annotations
if annotations == nil {
annotations = map[string]string{}
}
if f.Git.URL != nil {
annotations[annotationOpenShiftVcsUri] = *f.Git.URL
}
if f.Git.Revision != nil {
annotations[annotationOpenShiftVcsRef] = *f.Git.Revision
}
annotations[annotationOpenShiftVcsUri] = f.Git.URL
annotations[annotationOpenShiftVcsRef] = f.Git.Revision
return annotations
}

View File

@ -42,7 +42,7 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe
{
Name: "gitRepository",
Description: "Git repository that hosts the function project",
Default: pplnv1beta1.NewArrayOrString(*f.Git.URL),
Default: pplnv1beta1.NewArrayOrString(f.Git.URL),
},
{
Name: "gitRevision",
@ -116,18 +116,13 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe
func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.PipelineRun {
// ----- General properties
revision := ""
if f.Git.Revision != nil {
revision = *f.Git.Revision
}
contextDir := ""
if f.Builder == builders.S2I {
revision := f.Git.Revision
contextDir := f.Git.ContextDir
if contextDir == "" && f.Builder == builders.S2I {
// TODO(lkingland): could instead update S2I to interpret empty string
// as cwd, such that builder-specific code can be kept out of here.
contextDir = "."
}
if f.Git.ContextDir != nil {
contextDir = *f.Git.ContextDir
}
buildEnvs := &pplnv1beta1.ArrayOrString{
Type: pplnv1beta1.ParamTypeArray,
@ -148,7 +143,7 @@ func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.P
params := []pplnv1beta1.Param{
{
Name: "gitRepository",
Value: *pplnv1beta1.NewArrayOrString(*f.Git.URL),
Value: *pplnv1beta1.NewArrayOrString(f.Git.URL),
},
{
Name: "gitRevision",
@ -235,7 +230,7 @@ func getBuilderImage(f fn.Function) (name string) {
}
func getPipelineName(f fn.Function) string {
return fmt.Sprintf("%s-%s-%s-pipeline", f.Name, f.BuildType, f.Builder)
return fmt.Sprintf("%s-%s-pipeline", f.Name, f.Builder)
}
func getPipelineSecretName(f fn.Function) string {

View File

@ -13,7 +13,7 @@ import (
func Test_generatePipeline(t *testing.T) {
testGitRepo := "http://git-repo/git.git"
testGit := fn.Git{
URL: &testGitRepo,
URL: testGitRepo,
}
tests := []struct {

View File

@ -27,8 +27,6 @@
"registry",
"image",
"imageDigest",
"build",
"git",
"buildpacks",
"builder",
"volumes",
@ -63,13 +61,6 @@
"imageDigest": {
"type": "string"
},
"build": {
"enum": [
"local",
"git"
],
"type": "string"
},
"git": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Git"

View File

@ -12,7 +12,7 @@ func Deploy(t *testing.T, knFunc *TestShellCmdRunner, project *FunctionTestProje
var result TestShellCmdResult
if project.IsBuilt {
result = knFunc.Exec("deploy", "--path", project.ProjectPath, "--registry", GetRegistry(), "--build=disabled")
result = knFunc.Exec("deploy", "--path", project.ProjectPath, "--registry", GetRegistry(), "--build=false")
} else {
result = knFunc.Exec("deploy", "--path", project.ProjectPath, "--registry", GetRegistry())
}

View File

@ -1,50 +1,19 @@
package oncluster
import (
"fmt"
"os"
"testing"
yaml "gopkg.in/yaml.v2"
fn "knative.dev/kn-plugin-func"
common "knative.dev/kn-plugin-func/test/_common"
)
type Git struct {
URL string
Revision string
ContextDir string
}
// UpdateFuncYamlGit update func.yaml file by setting build to git as well as git fields.
func UpdateFuncYamlGit(t *testing.T, projectDir string, git Git) {
funcYamlPath := projectDir + "/func.yaml"
data, err := os.ReadFile(funcYamlPath)
// UpdateFuncGit updates a function's git settings
func UpdateFuncGit(t *testing.T, projectDir string, git fn.Git) {
f, err := fn.NewFunction(projectDir)
AssertNoError(t, err)
m := make(map[interface{}]interface{})
err = yaml.Unmarshal([]byte(data), &m)
f.Git = git
err = f.Write()
AssertNoError(t, err)
gitMap := make(map[interface{}]interface{})
m["build"] = "git"
m["git"] = gitMap
changeLog := fmt.Sprintln("build:", "git")
updateGitField := func(targetField string, targetValue string) {
if targetValue != "" {
gitMap[targetField] = targetValue
changeLog += fmt.Sprintln("git.", targetField, ":", targetValue)
}
}
updateGitField("url", git.URL)
updateGitField("revision", git.Revision)
updateGitField("contextDir", git.ContextDir)
outData, _ := yaml.Marshal(m)
err = os.WriteFile(funcYamlPath, outData, 0644)
AssertNoError(t, err)
t.Logf("func.yaml changed:\n%v", changeLog)
}
// GitInitialCommitAndPush Runs repeatable git commands used on every initial repository setup

View File

@ -36,11 +36,13 @@ func TestBasicDefault(t *testing.T) {
sh := GitInitialCommitAndPush(t, funcPath, remoteRepo.ExternalCloneURL)
// Update func.yaml build as git + url + context-dir
UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL})
// Deploy it
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath)
knFunc.Exec("deploy",
"-p", funcPath,
"-r", e2e.GetRegistry(),
"--remote",
"--git-url", remoteRepo.ClusterCloneURL,
)
defer knFunc.Exec("delete", "-p", funcPath)
// Assert "first revision" is returned
@ -56,7 +58,10 @@ func TestBasicDefault(t *testing.T) {
sh.Exec(`git push`)
// Re-Deploy Func
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath)
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--remote")
e2e.NewRevisionCheck(t, previousServiceRevision, funcName) // Wait New Service Revision
// -- Assertions --

View File

@ -42,10 +42,13 @@ func TestContextDirFunc(t *testing.T) {
// Initial commit to repository: git init + commit + push
GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL)
// Update func.yaml build as git + url + context-dir
UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL, ContextDir: funcContextDir})
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath)
knFunc.Exec("deploy",
"-p", funcPath,
"-r", e2e.GetRegistry(),
"--remote",
"--git-url", remoteRepo.ClusterCloneURL,
"--git-dir", funcContextDir,
)
defer knFunc.Exec("delete", "-p", funcPath)
// -- Assertions --

View File

@ -8,6 +8,7 @@ import (
"path/filepath"
"testing"
fn "knative.dev/kn-plugin-func"
common "knative.dev/kn-plugin-func/test/_common"
e2e "knative.dev/kn-plugin-func/test/_e2e"
)
@ -24,9 +25,13 @@ func TestFromCliBuildLocal(t *testing.T) {
defer os.RemoveAll(funcPath)
// Update func.yaml build as local + some fake url (it should not call it anyway)
UpdateFuncYamlGit(t, funcPath, Git{URL: "http://fake-repo/repo.git"})
UpdateFuncGit(t, funcPath, fn.Git{URL: "http://fake-repo/repo.git"})
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath, "--build", "local")
knFunc.Exec("deploy",
"-p", funcPath,
"-r", e2e.GetRegistry(),
// "--remote", // NOTE: Intentionally omitted
)
defer knFunc.Exec("delete", "-p", funcPath)
// -- Assertions --

View File

@ -4,17 +4,16 @@
package oncluster
/*
Tests on this file covers the scenarios when func.yaml is not modified (build: local)
and git build strategy is specified thru CLI.
Tests on this file covers the following scenarios:
A) Default Branch Test
func deploy --build=git --git-url=http://gitserver/myfunc.git
func deploy --remote --git-url=http://gitserver/myfunc.git
b) Feature Branch Test
func deploy --build=git --git-url=http://gitserver/myfunc.git --git-branch=feature/my-branch
func deploy --remote --git-url=http://gitserver/myfunc.git --git-branch=feature/my-branch
c) Context Dir test
func deploy --build=git --git-url=http://gitserver/myfunc.git --git-dir=functions/myfunc
func deploy --remote --git-url=http://gitserver/myfunc.git --git-dir=functions/myfunc
*/
import (
@ -50,7 +49,7 @@ func TestFromCliDefaultBranch(t *testing.T) {
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--build", "git",
"--remote",
"--git-url", remoteRepo.ClusterCloneURL)
defer knFunc.Exec("delete", "-p", funcPath)
@ -91,7 +90,7 @@ func TestFromCliFeatureBranch(t *testing.T) {
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--build", "git",
"--remote",
"--git-url", remoteRepo.ClusterCloneURL,
"--git-branch", "feature/branch")
@ -129,7 +128,7 @@ func TestFromCliContextDirFunc(t *testing.T) {
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--build", "git",
"--remote",
"--git-url", remoteRepo.ClusterCloneURL,
"--git-dir", funcContextDir)

View File

@ -18,6 +18,7 @@ import (
"strings"
"testing"
fn "knative.dev/kn-plugin-func"
common "knative.dev/kn-plugin-func/test/_common"
e2e "knative.dev/kn-plugin-func/test/_e2e"
)
@ -31,7 +32,7 @@ func TestFromFeatureBranch(t *testing.T) {
sh.Exec("git add index.js")
sh.Exec(`git commit -m "feature branch change"`)
sh.Exec("git push -u origin feature/branch")
UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: "feature/branch"})
UpdateFuncGit(t, funcProjectPath, fn.Git{URL: clusterCloneUrl, Revision: "feature/branch"})
}
assertBodyFn := func(response string) bool {
@ -54,7 +55,7 @@ func TestFromRevisionTag(t *testing.T) {
sh.Exec("git add index.js")
sh.Exec(`git commit -m "version 2"`)
sh.Exec("git push origin main")
UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: "tag-v1"})
UpdateFuncGit(t, funcProjectPath, fn.Git{URL: clusterCloneUrl, Revision: "tag-v1"})
}
assertBodyFn := func(response string) bool {
@ -77,7 +78,7 @@ func TestFromCommitHash(t *testing.T) {
sh.Exec(`git commit -m "version 2"`)
sh.Exec("git push origin main")
commitHash := strings.TrimSpace(gitRevParse.Stdout)
UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: commitHash})
UpdateFuncGit(t, funcProjectPath, fn.Git{URL: clusterCloneUrl, Revision: commitHash})
t.Logf("Revision Check: commit hash resolved to [%v]", commitHash)
}
@ -109,7 +110,10 @@ func GitRevisionCheck(
// Setup specific code
setupCodeFn(sh, funcPath, remoteRepo.ClusterCloneURL)
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath)
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--remote")
defer knFunc.Exec("delete", "-p", funcPath)
// -- Assertions --

View File

@ -56,7 +56,7 @@ func runtimeImpl(t *testing.T, lang string) {
knFunc.Exec("deploy",
"-r", e2e.GetRegistry(),
"-p", funcPath,
"--build", "git",
"--remote",
"--git-url", remoteRepo.ClusterCloneURL)
defer knFunc.Exec("delete", "-p", funcPath)