mirror of https://github.com/knative/func.git
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:
parent
966a150c58
commit
15713b2a75
41
client.go
41
client.go
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)()
|
||||
|
|
|
|||
76
cmd/build.go
76
cmd/build.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
509
cmd/deploy.go
509
cmd/deploy.go
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
21
function.go
21
function.go
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
2
go.mod
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 --
|
||||
|
|
|
|||
|
|
@ -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 --
|
||||
|
|
|
|||
|
|
@ -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 --
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 --
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue