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.
|
// XDG_CONFIG_HOME set, and no WithConfigPath was used.
|
||||||
DefaultConfigPath = ".config/func"
|
DefaultConfigPath = ".config/func"
|
||||||
|
|
||||||
// DefaultBuildType is the default build type for a function
|
|
||||||
DefaultBuildType = BuildTypeLocal
|
|
||||||
|
|
||||||
// RunDataDir holds transient runtime metadata
|
// RunDataDir holds transient runtime metadata
|
||||||
// By default it is excluded from source control.
|
// By default it is excluded from source control.
|
||||||
RunDataDir = ".func"
|
RunDataDir = ".func"
|
||||||
|
|
@ -528,7 +525,7 @@ func (c *Client) Create(cfg Function) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasFunc {
|
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
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunPipeline runs a Pipeline to Build and deploy the function at path.
|
// RunPipeline runs a Pipeline to build and deploy the function.
|
||||||
// In a parameter accepts git configuration options that we don't want to persist into func.yaml.
|
// Returned function contains applicable registry and deployed image name.
|
||||||
func (c *Client) RunPipeline(ctx context.Context, path string, git Git) (err error) {
|
func (c *Client) RunPipeline(ctx context.Context, f Function) (Function, error) {
|
||||||
|
var err error
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
c.progressListener.Stopping()
|
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
|
// Default function registry to the client's global registry
|
||||||
if f.Registry == "" {
|
if f.Registry == "" {
|
||||||
f.Registry = c.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.
|
// 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.
|
// 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 == "" {
|
||||||
if f.Image, err = f.ImageName(); err != nil {
|
if f.Image, err = f.ImageName(); err != nil {
|
||||||
return
|
return f, err
|
||||||
}
|
|
||||||
//Write (save) - Serialize the function to disk
|
|
||||||
//Will now contain populated image name.
|
|
||||||
if err = f.Write(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// Build and deploy function using Pipeline
|
||||||
err = c.pipelinesProvider.Run(ctx, f)
|
if err := c.pipelinesProvider.Run(ctx, f); err != nil {
|
||||||
if err != nil {
|
return f, fmt.Errorf("failed to run pipeline: %w", err)
|
||||||
err = fmt.Errorf("failed to run pipeline: %w", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Route(path string) (err error) {
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !f.Initialized() {
|
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
|
functionName = f.Name
|
||||||
cfg = f
|
cfg = f
|
||||||
|
|
|
||||||
|
|
@ -580,7 +580,7 @@ func TestClient_Run_DataDir(t *testing.T) {
|
||||||
t.Errorf(".gitignore does not include '/%v' ignore directive", fn.RunDataDir)
|
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.
|
// process, erroring if run on a directory uncreated.
|
||||||
func TestClient_Update(t *testing.T) {
|
func TestClient_Update(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
|
@ -749,6 +749,7 @@ func TestClient_Deploy_RegistryUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
if f.Image != expected { // DOES change to bob
|
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)
|
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.WithPipelinesProvider(mock.NewPipelinesProvider()),
|
||||||
fn.WithRegistry("example.com/alice"))
|
fn.WithRegistry("example.com/alice"))
|
||||||
|
|
||||||
repoUrl := "http://example-git.com/alice/myfunc.gi"
|
f := fn.Function{
|
||||||
git := fn.Git{
|
Name: "myfunc",
|
||||||
URL: &repoUrl,
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := fn.NewFunction(root)
|
f, err = fn.NewFunction(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -1089,11 +1092,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upon pipeline run, the function should be populated;
|
// 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)
|
|
||||||
}
|
|
||||||
f, err = fn.NewFunction(root)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
expected := "example.com/alice/myfunc:latest"
|
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)
|
t.Fatalf("expected image '%v', got '%v'", expected, f.Image)
|
||||||
}
|
}
|
||||||
expected = "example.com/alice"
|
expected = "example.com/alice"
|
||||||
if f.Registry != "example.com/alice" {
|
if f.Registry != expected {
|
||||||
t.Fatalf("expected registry '%v', got '%v'", expected, f.Registry)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Upon pipeline run, the function should be populated;
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
expected = "registry2.example.com/bob/myfunc:latest"
|
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)
|
t.Fatalf("expected image '%v', got '%v'", expected, f.Image)
|
||||||
}
|
}
|
||||||
expected = "example.com/alice"
|
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
|
// Note that according to current logic, the function's defined registry
|
||||||
// may be inaccurate. Consider an initial deploy to registryA, followed by
|
// may be inaccurate. Consider an initial deploy to registryA, followed by
|
||||||
// an explicit mutaiton of the function's .Image member.
|
// 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
|
// TestClient_Deploy_UnbuiltErrors ensures that a call to deploy a function
|
||||||
// fully created (ie. was only initialized, not actually built and deploys)
|
// which was not fully created (ie. was only initialized, not actually built
|
||||||
// yields an expected, and informative, error.
|
// or deployed) yields the expected error.
|
||||||
func TestClient_Deploy_UnbuiltErrors(t *testing.T) {
|
func TestClient_Deploy_UnbuiltErrors(t *testing.T) {
|
||||||
root := "testdata/example.com/testDeployUnbuilt" // Root from which to run the test
|
root := "testdata/example.com/testDeployUnbuilt" // Root from which to run the test
|
||||||
defer Using(t, root)()
|
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().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().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("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().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).")
|
cmd.Flags().StringP("platform", "", "", "Target platform to build (e.g. linux/amd64).")
|
||||||
setPathFlag(cmd)
|
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)
|
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) {
|
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()
|
config, err := newBuildConfig().Prompt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, terminal.InterruptErr) {
|
if errors.Is(err, terminal.InterruptErr) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the config
|
||||||
|
if err = config.Validate(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,35 +119,33 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
|
||||||
if config.Image != "" {
|
if config.Image != "" {
|
||||||
f.Image = 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
|
// Choose a builder based on the value of the --builder flag
|
||||||
var builder fn.Builder
|
var builder fn.Builder
|
||||||
if f.Builder == "" || cmd.Flags().Changed("builder") {
|
if f.Builder == builders.Pack {
|
||||||
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
|
|
||||||
}
|
|
||||||
builder = buildpacks.NewBuilder(
|
builder = buildpacks.NewBuilder(
|
||||||
buildpacks.WithName(builders.Pack),
|
buildpacks.WithName(builders.Pack),
|
||||||
buildpacks.WithVerbose(config.Verbose))
|
buildpacks.WithVerbose(config.Verbose))
|
||||||
} else if config.Builder == builders.S2I {
|
} else if f.Builder == builders.S2I {
|
||||||
builder = s2i.NewBuilder(
|
builder = s2i.NewBuilder(
|
||||||
s2i.WithName(builders.S2I),
|
s2i.WithName(builders.S2I),
|
||||||
s2i.WithVerbose(config.Verbose),
|
s2i.WithPlatform(config.Platform),
|
||||||
s2i.WithPlatform(config.Platform))
|
s2i.WithVerbose(config.Verbose))
|
||||||
}
|
} else {
|
||||||
|
err = fmt.Errorf("builder '%v' is not recognized", f.Builder)
|
||||||
// Use the user-provided builder image, if supplied
|
return
|
||||||
if config.BuilderImage != "" {
|
|
||||||
f.BuilderImages[config.Builder] = config.BuilderImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, done := newClient(ClientConfig{Verbose: config.Verbose},
|
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 == "" {
|
if client.Registry() == "" && f.Registry == "" && f.Image == "" {
|
||||||
// It is not necessary that we validate here, since the client API has
|
// 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
|
// 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
|
return ErrRegistryRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,3 +288,14 @@ func (c buildConfig) Prompt() (buildConfig, error) {
|
||||||
err = survey.Ask(qs, &c)
|
err = survey.Ask(qs, &c)
|
||||||
return c, err
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
fn "knative.dev/kn-plugin-func"
|
fn "knative.dev/kn-plugin-func"
|
||||||
|
|
@ -20,11 +19,10 @@ func TestBuild_ImageFlag(t *testing.T) {
|
||||||
root, cleanup := Mktemp(t)
|
root, cleanup := Mktemp(t)
|
||||||
defer cleanup()
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create build command that will use a mock builder.
|
|
||||||
cmd := NewBuildCmd(NewClientFactory(func() *fn.Client {
|
cmd := NewBuildCmd(NewClientFactory(func() *fn.Client {
|
||||||
return fn.New(fn.WithBuilder(builder))
|
return fn.New(fn.WithBuilder(builder))
|
||||||
}))
|
}))
|
||||||
|
|
@ -33,7 +31,7 @@ func TestBuild_ImageFlag(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected error")
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now load the function and ensure that the image is set correctly.
|
// 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
|
// TestDeploy_RegistryOrImageRequired ensures that when no registry or image are
|
||||||
// provided, and the client has not been instantiated with a default registry,
|
// provided (or exist on the function already), and the client has not been
|
||||||
// an ErrRegistryRequired is received.
|
// instantiated with a default registry, an ErrRegistryRequired is received.
|
||||||
func TestBuild_RegistryOrImageRequired(t *testing.T) {
|
func TestBuild_RegistryOrImageRequired(t *testing.T) {
|
||||||
testRegistryOrImageRequired(NewBuildCmd, t)
|
t.Helper()
|
||||||
}
|
root, rm := Mktemp(t)
|
||||||
|
defer rm()
|
||||||
|
|
||||||
// TestBuild_ImageAndRegistry
|
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
|
||||||
func TestBuild_ImageAndRegistry(t *testing.T) {
|
t.Fatal(err)
|
||||||
testRegistryOrImageRequired(NewBuildCmd, t)
|
}
|
||||||
|
|
||||||
|
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
|
// TestBuild_InvalidRegistry ensures that providing an invalid resitry
|
||||||
|
|
@ -83,101 +100,61 @@ func TestBuild_BuilderValidated(t *testing.T) {
|
||||||
testBuilderValidated(NewBuildCmd, t)
|
testBuilderValidated(NewBuildCmd, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuild_runBuild(t *testing.T) {
|
// TestBuild_Push ensures that the build command properly pushes and respects
|
||||||
tests := []struct {
|
// the --push flag.
|
||||||
name string
|
// - Push triggered after a successful build
|
||||||
pushFlag bool
|
// - Push not triggered after an unsuccessful build
|
||||||
fileContents string
|
// - Push can be disabled
|
||||||
shouldBuild bool
|
func TestBuild_Push(t *testing.T) {
|
||||||
shouldPush bool
|
root, rm := Mktemp(t)
|
||||||
wantErr bool
|
defer rm()
|
||||||
}{
|
|
||||||
{
|
f := fn.Function{
|
||||||
name: "push flag triggers push after build",
|
Root: root,
|
||||||
pushFlag: true,
|
Name: "myfunc",
|
||||||
fileContents: `name: test-func
|
Runtime: "go",
|
||||||
runtime: go
|
Registry: "example.com/alice",
|
||||||
created: 2009-11-10 23:00:00`,
|
}
|
||||||
shouldBuild: true,
|
if err := fn.New().Create(f); err != nil {
|
||||||
shouldPush: true,
|
t.Fatal(err)
|
||||||
},
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
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")
|
var (
|
||||||
if err != nil {
|
builder = mock.NewBuilder()
|
||||||
t.Fatalf("temp dir couldn't be created %v", err)
|
pusher = mock.NewPusher()
|
||||||
}
|
cmd = NewBuildCmd(NewClientFactory(func() *fn.Client {
|
||||||
t.Log("tempDir created:", tempDir)
|
return fn.New(fn.WithRegistry(TestRegistry), fn.WithBuilder(builder), fn.WithPusher(pusher))
|
||||||
t.Cleanup(func() {
|
}))
|
||||||
os.RemoveAll(tempDir)
|
)
|
||||||
})
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
fullPath := tempDir + "/func.yaml"
|
// Assert there was no push
|
||||||
tempFile, err := os.Create(fullPath)
|
if pusher.PushInvoked {
|
||||||
if err != nil {
|
t.Fatal("push should not be invoked by default")
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.SetArgs([]string{
|
// Execute with push enabled
|
||||||
"--path=" + tempDir,
|
cmd.SetArgs([]string{"--push"})
|
||||||
fmt.Sprintf("--push=%t", tt.pushFlag),
|
if err := cmd.Execute(); err != nil {
|
||||||
"--registry=docker.io/tigerteam",
|
t.Fatal(err)
|
||||||
})
|
}
|
||||||
|
|
||||||
err = cmd.Execute()
|
// Assert there was a push
|
||||||
if tt.wantErr != (err != nil) {
|
if !pusher.PushInvoked {
|
||||||
t.Errorf("Wanted error %v but actually got %v", tt.wantErr, err)
|
t.Fatal("push should be invoked when requested and a successful build")
|
||||||
}
|
}
|
||||||
|
|
||||||
if mockBuilder.BuildInvoked != tt.shouldBuild {
|
// Exeute with push enabled but with a failed build
|
||||||
t.Errorf("Build execution expected: %v but was actually %v", tt.shouldBuild, mockBuilder.BuildInvoked)
|
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) {
|
// Assert push was not invoked when the build failed
|
||||||
t.Errorf("Push execution expected: %v but was actually mockPusher invoked: %v failPusher invoked %v", tt.shouldPush, mockPusher.PushInvoked, failPusher.PushInvoked)
|
if pusher.PushInvoked {
|
||||||
}
|
t.Fatal("push should not be invoked on a failed build")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
fn "knative.dev/kn-plugin-func"
|
fn "knative.dev/kn-plugin-func"
|
||||||
"knative.dev/kn-plugin-func/builders"
|
|
||||||
"knative.dev/kn-plugin-func/knative"
|
"knative.dev/kn-plugin-func/knative"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -130,12 +129,20 @@ func CompleteBuilderImageList(cmd *cobra.Command, args []string, complete string
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompleteDeployBuildType(cmd *cobra.Command, args []string, complete string) (buildTypes []string, directive cobra.ShellCompDirective) {
|
func CompleteBuilderList(cmd *cobra.Command, args []string, complete string) (matches []string, d cobra.ShellCompDirective) {
|
||||||
buildTypes = fn.AllBuildTypes()
|
d = cobra.ShellCompDirectiveNoFileComp
|
||||||
directive = cobra.ShellCompDirectiveDefault
|
matches = []string{}
|
||||||
|
|
||||||
|
if len(complete) == 0 {
|
||||||
|
matches = KnownBuilders()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range KnownBuilders() {
|
||||||
|
if strings.HasPrefix(b, complete) {
|
||||||
|
matches = append(matches, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
@ -28,30 +29,79 @@ import (
|
||||||
func NewDeployCmd(newClient ClientFactory) *cobra.Command {
|
func NewDeployCmd(newClient ClientFactory) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "deploy",
|
Use: "deploy",
|
||||||
Short: "Deploy a function",
|
Short: "Deploy a Function",
|
||||||
Long: `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.
|
SYNOPSIS
|
||||||
The function is picked up from the project in the current directory or from the path provided
|
{{.Name}} deploy [-R|--remote] [-r|--registry] [-i|--image] [-n|--namespace]
|
||||||
with --path.
|
[-e|env] [-g|--git-url] [-t|git-branch] [-d|--git-dir]
|
||||||
If not already configured, either --registry or --image has to be provided and is then stored
|
[-b|--build] [--builder] [--builder-image] [-p|--push]
|
||||||
in the configuration file.
|
[--platform] [-c|--confirm] [-v|--verbose]
|
||||||
|
|
||||||
If the function is already deployed, it is updated with a new container image
|
DESCRIPTION
|
||||||
that is pushed to an image registry, and finally the function's Knative service is updated.
|
|
||||||
`,
|
Deploys a function to the currently configured Knative-enabled cluster.
|
||||||
Example: `
|
|
||||||
# Build and deploy the function from the current directory's project. The image will be
|
By default the function in the current working directory is deployed, or at
|
||||||
# pushed to "quay.io/myuser/<function name>" and deployed as Knative service with the
|
the path defined by --path.
|
||||||
# same name as the function to the currently connected cluster.
|
|
||||||
{{.Name}} deploy --registry quay.io/myuser
|
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"},
|
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)")
|
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-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-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("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)))
|
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 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()))
|
// Flags shared with Build (specifically related to the build step):
|
||||||
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().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("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().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, "Attempt to push the function image to registry before deploying (Env: $FUNC_PUSH)")
|
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("platform", "", "", "Target platform to build (e.g. linux/amd64).")
|
||||||
|
cmd.Flags().StringP("namespace", "n", "", "Deploy into a specific namespace. (Env: $FUNC_NAMESPACE)")
|
||||||
setPathFlag(cmd)
|
setPathFlag(cmd)
|
||||||
|
|
||||||
if err := cmd.RegisterFlagCompletionFunc("build", CompleteDeployBuildType); err != nil {
|
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuilderList); err != nil {
|
||||||
fmt.Println("internal: error while calling RegisterFlagCompletionFunc: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.RegisterFlagCompletionFunc("builder", CompleteBuildersList); err != nil {
|
|
||||||
fmt.Println("internal: error while calling RegisterFlagCompletionFunc: ", err)
|
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
|
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) {
|
func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error) {
|
||||||
|
// Create a deploy config from environment variables and flags
|
||||||
config, err := newDeployConfig(cmd)
|
config, err := newDeployConfig(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prompt the user to potentially change config interactively.
|
||||||
config, err = config.Prompt()
|
config, err = config.Prompt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == terminal.InterruptErr {
|
if err == terminal.InterruptErr {
|
||||||
|
|
@ -106,17 +161,9 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//if --image contains '@', validate image digest and disable build and push if not set, otherwise return an error
|
// Validate the config
|
||||||
// TODO(lkingland): this logic could be assimilated into the Deploy Config
|
if err = config.Validate(); err != nil {
|
||||||
// struct and it's constructor.
|
return
|
||||||
imageSplit := strings.Split(config.Image, "@")
|
|
||||||
imageDigestProvided := false
|
|
||||||
|
|
||||||
if len(imageSplit) == 2 {
|
|
||||||
if config, err = parseImageDigest(imageSplit, config, cmd); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imageDigestProvided = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the function, and if it exists (path initialized as a function), merge
|
// 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 != "" {
|
if config.Image != "" {
|
||||||
f.Image = 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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Envs, _, err = mergeEnvs(f.Envs, config.EnvToUpdate, config.EnvToRemove)
|
f.Envs, _, err = mergeEnvs(f.Envs, config.EnvToUpdate, config.EnvToRemove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBuildType := config.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 build type has been explicitly set as flag, validate it and override function config
|
if err = ValidateBuilder(f.Builder); err != nil {
|
||||||
if config.BuildType != "" {
|
return
|
||||||
err = validateBuildType(config.BuildType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentBuildType = f.BuildType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if imageDigestProvided {
|
// Choose a builder based on the value of the --builder flag and a possible
|
||||||
// TODO(lkingland): This could instead be part of the config, relying on
|
// override for the build image for that builder to use from the optional
|
||||||
// zero values rather than a flag indicating "Image Digest was Provided"
|
// builder-image flag.
|
||||||
f.ImageDigest = imageSplit[1] // save image digest if provided in --image
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose a builder based on the value of the --builder flag
|
|
||||||
var builder fn.Builder
|
var builder fn.Builder
|
||||||
if f.Builder == "" || cmd.Flags().Changed("builder") {
|
if f.Builder == builders.Pack {
|
||||||
f.Builder = config.Builder
|
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 {
|
} else {
|
||||||
config.Builder = f.Builder
|
err = fmt.Errorf("builder '%v' is not recognized", f.Builder)
|
||||||
}
|
return
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, done := newClient(ClientConfig{Namespace: f.Namespace, Verbose: config.Verbose},
|
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
|
// Default Client Registry, Function Registry or explicit Image required
|
||||||
if client.Registry() == "" && f.Registry == "" && f.Image == "" {
|
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
|
return ErrRegistryRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: curently need to preemptively write out function state until
|
// Perform the deployment either remote or local.
|
||||||
// the API is updated to use instances, at which point we will only
|
if config.Remote {
|
||||||
// write on success.
|
if f.Git.URL == "" {
|
||||||
if err = f.Write(); err != nil {
|
return ErrURLRequired // Provides CLI-specific help text
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch currentBuildType {
|
|
||||||
case fn.BuildTypeLocal, "":
|
|
||||||
if config.GitURL != "" || config.GitDir != "" || config.GitBranch != "" {
|
|
||||||
return fmt.Errorf("remote git arguments require the --build=git flag")
|
|
||||||
}
|
}
|
||||||
if err := client.Build(cmd.Context(), config.Path); err != nil {
|
// Invoke a remote build/push/deploy pipeline
|
||||||
return err
|
// 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:
|
} else {
|
||||||
git := f.Git
|
if err = f.Write(); err != nil { // TODO: remove when client API uses 'f'
|
||||||
|
return
|
||||||
if config.GitURL != "" {
|
}
|
||||||
git.URL = &config.GitURL
|
if build(config.Build, f, client) { // --build or "auto" with FS changes
|
||||||
if strings.Contains(config.GitURL, "#") {
|
if err = client.Build(cmd.Context(), f.Root); err != nil {
|
||||||
parts := strings.Split(config.GitURL, "#")
|
return
|
||||||
git.URL = &parts[0]
|
|
||||||
git.Revision = &parts[1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if f, err = fn.NewFunction(f.Root); err != nil { // TODO: remove when client API uses 'f'
|
||||||
if config.GitBranch != "" {
|
return
|
||||||
git.Revision = &config.GitBranch
|
|
||||||
}
|
}
|
||||||
|
if config.Push {
|
||||||
if config.GitDir != "" {
|
if err = client.Push(cmd.Context(), f.Root); err != nil {
|
||||||
git.ContextDir = &config.GitDir
|
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 {
|
// mutations persisted on success
|
||||||
if err := client.Push(cmd.Context(), config.Path); err != nil {
|
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 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
|
// 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 {
|
type deployConfig struct {
|
||||||
buildConfig
|
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
|
// Namespace override for the deployed function. If provided, the
|
||||||
// underlying platform will be instructed to deploy the function to the given
|
// underlying platform will be instructed to deploy the function to the given
|
||||||
// namespace (if such a setting is applicable; such as for Kubernetes
|
// 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.
|
// (~/.kube/config) in the case of Kubernetes.
|
||||||
Namespace string
|
Namespace string
|
||||||
|
|
||||||
// Build the associated function before deploying.
|
|
||||||
BuildType string
|
|
||||||
|
|
||||||
// Envs passed via cmd to be added/updated
|
// Envs passed via cmd to be added/updated
|
||||||
EnvToUpdate *util.OrderedMap
|
EnvToUpdate *util.OrderedMap
|
||||||
|
|
||||||
|
|
@ -421,6 +516,9 @@ type deployConfig struct {
|
||||||
|
|
||||||
// Directory in the git repo where the function is located
|
// Directory in the git repo where the function is located
|
||||||
GitDir string
|
GitDir string
|
||||||
|
|
||||||
|
// ImageDigest is automatically split off an --image tag
|
||||||
|
ImageDigest string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDeployConfig creates a buildConfig populated from command flags and
|
// newDeployConfig creates a buildConfig populated from command flags and
|
||||||
|
|
@ -431,23 +529,24 @@ func newDeployConfig(cmd *cobra.Command) (deployConfig, error) {
|
||||||
return deployConfig{}, err
|
return deployConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to know whether the `build`` flag had been explicitly set,
|
c := deployConfig{
|
||||||
// to distinguish between unset and default value.
|
|
||||||
var buildType string
|
|
||||||
if viper.IsSet("build") {
|
|
||||||
buildType = viper.GetString("build")
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployConfig{
|
|
||||||
buildConfig: newBuildConfig(),
|
buildConfig: newBuildConfig(),
|
||||||
|
Build: viper.GetString("build"),
|
||||||
|
Remote: viper.GetBool("remote"),
|
||||||
Namespace: viper.GetString("namespace"),
|
Namespace: viper.GetString("namespace"),
|
||||||
BuildType: buildType,
|
|
||||||
EnvToUpdate: envToUpdate,
|
EnvToUpdate: envToUpdate,
|
||||||
EnvToRemove: envToRemove,
|
EnvToRemove: envToRemove,
|
||||||
GitURL: viper.GetString("git-url"),
|
GitURL: viper.GetString("git-url"),
|
||||||
GitBranch: viper.GetString("git-branch"),
|
GitBranch: viper.GetString("git-branch"),
|
||||||
GitDir: viper.GetString("git-dir"),
|
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.
|
// 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{
|
var qs = []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "namespace",
|
||||||
|
Prompt: &survey.Input{
|
||||||
|
Message: "Destination namespace:",
|
||||||
|
Default: c.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "path",
|
Name: "path",
|
||||||
Prompt: &survey.Input{
|
Prompt: &survey.Input{
|
||||||
Message: "Project path:",
|
Message: "Function source path:",
|
||||||
Default: c.Path,
|
Default: c.Path,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -502,82 +608,109 @@ func (c deployConfig) Prompt() (deployConfig, error) {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrInvalidBuildType indicates that the passed build type was invalid.
|
// Validate the config passes an initial consistency check
|
||||||
type ErrInvalidBuildType error
|
func (c deployConfig) Validate() (err error) {
|
||||||
|
// Bubble validation
|
||||||
// ValidateBuildType validatest that the input Build type is valid for deploy command
|
if err = c.buildConfig.Validate(); err != nil {
|
||||||
func validateBuildType(buildType string) error {
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(imageSplit[1][7:]) != 64 {
|
// Can not enable build when specifying an --image
|
||||||
return config, fmt.Errorf("sha256 hash in '%s' from --image has the wrong length (%d), should be 64", imageSplit[1], len(imageSplit[1][7:]))
|
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
|
// Can not push when specifying an --image
|
||||||
if cmd.Flags().Changed("build") && config.BuildType != "disabled" {
|
if c.ImageDigest != "" && c.Push {
|
||||||
return config, fmt.Errorf("the --build flag '%s' is not valid when using --image with digest", config.BuildType)
|
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
|
// Git settings are only avaolabe with --remote
|
||||||
if cmd.Flags().Changed("push") && config.Push {
|
if (c.GitURL != "" || c.GitDir != "" || c.GitBranch != "") && !c.Remote {
|
||||||
return config, fmt.Errorf("the --push flag '%v' is not valid when using --image with digest", config.Push)
|
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"
|
// --build can be "auto"|true|false
|
||||||
config.Push = false
|
if c.Build != "auto" {
|
||||||
config.Image = imageSplit[0]
|
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)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currNamespace, err := k8s.GetNamespace("")
|
return
|
||||||
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)
|
|
||||||
|
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 {
|
if len(digest[7:]) != 64 {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: New namespace '%s' does not match current namespace '%s'\n", namespace, funcNamespace)
|
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:
|
var ErrRegistryRequired = errors.New(`A container registry is required. For example:
|
||||||
--registry docker.io/myusername
|
--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)`)
|
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
|
// prints bo only on --verbose. Possible future tweak, as I don't want to
|
||||||
// make functional changes during a refactor.
|
// make functional changes during a refactor.
|
||||||
if config.Namespace != "" && !config.AllNamespaces {
|
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 {
|
} else {
|
||||||
fmt.Println("No functions found")
|
fmt.Println("no functions found")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ func Test_newPromptForCredentials(t *testing.T) {
|
||||||
go func() {
|
go func() {
|
||||||
chars := expectedCreds.Username + enter + expectedCreds.Password + enter
|
chars := expectedCreds.Username + enter + expectedCreds.Password + enter
|
||||||
for _, ch := range chars {
|
for _, ch := range chars {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
_, _ = console.Send(string(ch))
|
_, _ = 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
|
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)
|
function, err := fn.NewFunction(config.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -79,7 +77,6 @@ func runRun(cmd *cobra.Command, args []string, newClient ClientFactory) (err err
|
||||||
if !function.Initialized() {
|
if !function.Initialized() {
|
||||||
return fmt.Errorf("the given path '%v' does not contain an initialized function", config.Path)
|
return fmt.Errorf("the given path '%v' does not contain an initialized function", config.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updated int
|
var updated int
|
||||||
function.Envs, updated, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
|
function.Envs, updated, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -108,20 +105,20 @@ func runRun(cmd *cobra.Command, args []string, newClient ClientFactory) (err err
|
||||||
return
|
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
|
// Otherwise, --build should parse to a truthy value which indicates an explicit
|
||||||
// override.
|
// override.
|
||||||
} else {
|
} else {
|
||||||
build, err := strconv.ParseBool(config.Build)
|
build, err := strconv.ParseBool(config.Build)
|
||||||
if err != nil {
|
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 build {
|
||||||
if err = client.Build(cmd.Context(), config.Path); err != nil {
|
if err = client.Build(cmd.Context(), config.Path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
* 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)
|
* 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
|
## Configuring for local access
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ The nodes should now be able to ping each other using their wireguard-protected
|
||||||
```
|
```
|
||||||
wg show
|
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]
|
[Interface]
|
||||||
Address=10.10.10.2/32
|
Address=10.10.10.2/32
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Serverless functions
|
||||||
|
|
||||||
### Synopsis
|
### 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
|
Create, build and deploy Knative functions
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ EXAMPLES
|
||||||
* [func config](func_config.md) - Configure a function
|
* [func config](func_config.md) - Configure a function
|
||||||
* [func create](func_create.md) - Create a function project
|
* [func create](func_create.md) - Create a function project
|
||||||
* [func delete](func_delete.md) - Undeploy a function
|
* [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 info](func_info.md) - Show details of a function
|
||||||
* [func invoke](func_invoke.md) - Invoke a function
|
* [func invoke](func_invoke.md) - Invoke a function
|
||||||
* [func languages](func_languages.md) - List available function language runtimes
|
* [func languages](func_languages.md) - List available function language runtimes
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,102 @@
|
||||||
## func deploy
|
## func deploy
|
||||||
|
|
||||||
Deploy a function
|
Deploy a Function
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
Deploy a function
|
|
||||||
|
|
||||||
Builds a container image for the function and deploys it to the connected Knative enabled cluster.
|
NAME
|
||||||
The function is picked up from the project in the current directory or from the path provided
|
func deploy - Deploy a Function
|
||||||
with --path.
|
|
||||||
If not already configured, either --registry or --image has to be provided and is then stored
|
SYNOPSIS
|
||||||
in the configuration file.
|
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
|
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
|
### Options
|
||||||
|
|
||||||
```
|
```
|
||||||
--build string Build specifies the way the function should be built. Supported types are "disabled", "local" or "git" (Env: $FUNC_BUILD) (default "local")
|
--build string[="true"] Build the function. [auto|true|false]. [Env: $FUNC_BUILD] (default "auto")
|
||||||
-b, --builder string build strategy to use when creating the underlying image. Currently supported build strategies are "pack" and "s2i". (default "pack")
|
-b, --builder string builder to use when creating the underlying image. Currently supported builders are "pack" and "s2i". (default "pack")
|
||||||
--builder-image string builder image, either an as a an image name or a mapping name.
|
--builder-image string The image the specified builder should use; either an as an image name or a mapping. ($FUNC_BUILDER_IMAGE)
|
||||||
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)
|
||||||
-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-).
|
||||||
-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)
|
||||||
-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)
|
||||||
-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)
|
||||||
-g, --git-url string Repo url to push the code to be built (Env: $FUNC_GIT_URL)
|
-h, --help help for deploy
|
||||||
-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)
|
||||||
-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 ".")
|
||||||
-p, --path string Path to the project directory (Env: $FUNC_PATH) (default ".")
|
--platform string Target platform to build (e.g. linux/amd64).
|
||||||
--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)
|
||||||
-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 '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)
|
||||||
-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)
|
--remote Trigger a remote deployment. Default is to deploy and build from the local system: $FUNC_REMOTE)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### 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
|
// SHA256 hash of the latest image that has been built
|
||||||
ImageDigest string `yaml:"imageDigest"`
|
ImageDigest string `yaml:"imageDigest"`
|
||||||
|
|
||||||
// BuildType represents the specified way of building the fuction
|
// Git stores information about an optionally associated git repository.
|
||||||
// ie. "local" or "git"
|
Git Git `yaml:"git,omitempty"`
|
||||||
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"`
|
|
||||||
|
|
||||||
// BuilderImages define optional explicit builder images to use by
|
// BuilderImages define optional explicit builder images to use by
|
||||||
// builder implementations in leau of the in-code defaults. They key
|
// builder implementations in leau of the in-code defaults. They key
|
||||||
|
|
@ -145,9 +140,6 @@ func NewFunctionWith(defaults Function) Function {
|
||||||
if defaults.Template == "" {
|
if defaults.Template == "" {
|
||||||
defaults.Template = DefaultTemplate
|
defaults.Template = DefaultTemplate
|
||||||
}
|
}
|
||||||
if defaults.BuildType == "" {
|
|
||||||
defaults.BuildType = DefaultBuildType
|
|
||||||
}
|
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,12 +184,6 @@ func (f Function) Validate() error {
|
||||||
return errors.New("function root path is required")
|
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
|
var ctr int
|
||||||
errs := [][]string{
|
errs := [][]string{
|
||||||
validateVolumes(f.Volumes),
|
validateVolumes(f.Volumes),
|
||||||
|
|
@ -205,8 +191,7 @@ func (f Function) Validate() error {
|
||||||
ValidateEnvs(f.Envs),
|
ValidateEnvs(f.Envs),
|
||||||
validateOptions(f.Options),
|
validateOptions(f.Options),
|
||||||
ValidateLabels(f.Labels),
|
ValidateLabels(f.Labels),
|
||||||
ValidateBuildType(f.BuildType, true, false),
|
validateGit(f.Git),
|
||||||
validateGit(f.Git, mandatoryGitOption),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var b strings.Builder
|
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 {
|
type Git struct {
|
||||||
URL *string `yaml:"url,omitempty"`
|
URL string `yaml:"url,omitempty"`
|
||||||
Revision *string `yaml:"revision,omitempty"`
|
Revision string `yaml:"revision,omitempty"`
|
||||||
ContextDir *string `yaml:"contextDir,omitempty"`
|
ContextDir string `yaml:"contextDir,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateGit validates input Git option from function config
|
// 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) (errors []string) {
|
||||||
func validateGit(git Git, mandatoryGit bool) (errors []string) {
|
if git.URL != "" {
|
||||||
if git.URL != nil {
|
_, err := giturls.ParseTransport(git.URL)
|
||||||
_, err := giturls.ParseTransport(*git.URL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err = giturls.ParseScp(*git.URL)
|
_, err = giturls.ParseScp(git.URL)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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()
|
originalErr := err.Error()
|
||||||
if !strings.HasSuffix(originalErr, "is not a valid transport") {
|
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)
|
errors = append(errors, errMsg)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if mandatoryGit {
|
|
||||||
errors = append(errors, "\"git.url\" must be specified for this \"build\" type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,87 +2,71 @@ package function
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"knative.dev/pkg/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_validateGit(t *testing.T) {
|
func Test_validateGit(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
git Git
|
git Git
|
||||||
mandatoryGit bool
|
errs int
|
||||||
errs int
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"correct 'Git - only URL https",
|
"correct 'Git - only URL https",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("https://myrepo/foo.git"),
|
URL: "https://myrepo/foo.git",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"correct 'Git - only URL scp",
|
"correct 'Git - only URL scp",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("git@myrepo:foo.git"),
|
URL: "git@myrepo:foo.git",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"correct 'Git - URL + revision",
|
"correct 'Git - URL + revision",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("https://myrepo/foo.git"),
|
URL: "https://myrepo/foo.git",
|
||||||
Revision: ptr.String("mybranch"),
|
Revision: "mybranch",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"correct 'Git - URL + context-dir",
|
"correct 'Git - URL + context-dir",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("https://myrepo/foo.git"),
|
URL: "https://myrepo/foo.git",
|
||||||
ContextDir: ptr.String("my-folder"),
|
ContextDir: "my-folder",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"correct 'Git - URL + revision & context-dir",
|
"correct 'Git - URL + revision & context-dir",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("https://myrepo/foo.git"),
|
URL: "https://myrepo/foo.git",
|
||||||
Revision: ptr.String("mybranch"),
|
Revision: "mybranch",
|
||||||
ContextDir: ptr.String("my-folder"),
|
ContextDir: "my-folder",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"incorrect 'Git - bad URL",
|
"incorrect 'Git - bad URL",
|
||||||
Git{
|
Git{
|
||||||
URL: ptr.String("foo"),
|
URL: "foo",
|
||||||
},
|
},
|
||||||
true,
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"incorrect 'Git - missing URL",
|
|
||||||
Git{},
|
|
||||||
true,
|
|
||||||
1,
|
1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"correct 'Git - not mandatory",
|
"correct 'Git - not mandatory",
|
||||||
Git{},
|
Git{},
|
||||||
false,
|
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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)
|
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/api v0.23.5
|
||||||
k8s.io/apimachinery v0.23.5
|
k8s.io/apimachinery v0.23.5
|
||||||
k8s.io/client-go v1.5.2
|
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/client v0.31.1
|
||||||
knative.dev/eventing v0.31.3-0.20220802083815-e345f5f3695d
|
knative.dev/eventing v0.31.3-0.20220802083815-e345f5f3695d
|
||||||
knative.dev/hack v0.0.0-20220629135029-9e09abcd61f0
|
knative.dev/hack v0.0.0-20220629135029-9e09abcd61f0
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,18 @@ type Deployer struct {
|
||||||
decorator DeployDecorator
|
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 {
|
func NewDeployer(opts ...DeployerOpt) *Deployer {
|
||||||
d := &Deployer{}
|
d := &Deployer{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,37 @@
|
||||||
package knative
|
package knative
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
fn "knative.dev/kn-plugin-func"
|
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) {
|
func Test_setHealthEndpoints(t *testing.T) {
|
||||||
f := fn.Function{
|
f := fn.Function{
|
||||||
Name: "testing",
|
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 {
|
if annotations == nil {
|
||||||
annotations = map[string]string{}
|
annotations = map[string]string{}
|
||||||
}
|
}
|
||||||
if f.Git.URL != nil {
|
annotations[annotationOpenShiftVcsUri] = f.Git.URL
|
||||||
annotations[annotationOpenShiftVcsUri] = *f.Git.URL
|
annotations[annotationOpenShiftVcsRef] = f.Git.Revision
|
||||||
}
|
|
||||||
if f.Git.Revision != nil {
|
|
||||||
annotations[annotationOpenShiftVcsRef] = *f.Git.Revision
|
|
||||||
}
|
|
||||||
|
|
||||||
return annotations
|
return annotations
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe
|
||||||
{
|
{
|
||||||
Name: "gitRepository",
|
Name: "gitRepository",
|
||||||
Description: "Git repository that hosts the function project",
|
Description: "Git repository that hosts the function project",
|
||||||
Default: pplnv1beta1.NewArrayOrString(*f.Git.URL),
|
Default: pplnv1beta1.NewArrayOrString(f.Git.URL),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "gitRevision",
|
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 {
|
func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.PipelineRun {
|
||||||
|
|
||||||
// ----- General properties
|
revision := f.Git.Revision
|
||||||
revision := ""
|
contextDir := f.Git.ContextDir
|
||||||
if f.Git.Revision != nil {
|
if contextDir == "" && f.Builder == builders.S2I {
|
||||||
revision = *f.Git.Revision
|
// 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.Builder == builders.S2I {
|
|
||||||
contextDir = "."
|
contextDir = "."
|
||||||
}
|
}
|
||||||
if f.Git.ContextDir != nil {
|
|
||||||
contextDir = *f.Git.ContextDir
|
|
||||||
}
|
|
||||||
|
|
||||||
buildEnvs := &pplnv1beta1.ArrayOrString{
|
buildEnvs := &pplnv1beta1.ArrayOrString{
|
||||||
Type: pplnv1beta1.ParamTypeArray,
|
Type: pplnv1beta1.ParamTypeArray,
|
||||||
|
|
@ -148,7 +143,7 @@ func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.P
|
||||||
params := []pplnv1beta1.Param{
|
params := []pplnv1beta1.Param{
|
||||||
{
|
{
|
||||||
Name: "gitRepository",
|
Name: "gitRepository",
|
||||||
Value: *pplnv1beta1.NewArrayOrString(*f.Git.URL),
|
Value: *pplnv1beta1.NewArrayOrString(f.Git.URL),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "gitRevision",
|
Name: "gitRevision",
|
||||||
|
|
@ -235,7 +230,7 @@ func getBuilderImage(f fn.Function) (name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPipelineName(f fn.Function) 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 {
|
func getPipelineSecretName(f fn.Function) string {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
func Test_generatePipeline(t *testing.T) {
|
func Test_generatePipeline(t *testing.T) {
|
||||||
testGitRepo := "http://git-repo/git.git"
|
testGitRepo := "http://git-repo/git.git"
|
||||||
testGit := fn.Git{
|
testGit := fn.Git{
|
||||||
URL: &testGitRepo,
|
URL: testGitRepo,
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@
|
||||||
"registry",
|
"registry",
|
||||||
"image",
|
"image",
|
||||||
"imageDigest",
|
"imageDigest",
|
||||||
"build",
|
|
||||||
"git",
|
|
||||||
"buildpacks",
|
"buildpacks",
|
||||||
"builder",
|
"builder",
|
||||||
"volumes",
|
"volumes",
|
||||||
|
|
@ -63,13 +61,6 @@
|
||||||
"imageDigest": {
|
"imageDigest": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"build": {
|
|
||||||
"enum": [
|
|
||||||
"local",
|
|
||||||
"git"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"git": {
|
"git": {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "#/definitions/Git"
|
"$ref": "#/definitions/Git"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ func Deploy(t *testing.T, knFunc *TestShellCmdRunner, project *FunctionTestProje
|
||||||
|
|
||||||
var result TestShellCmdResult
|
var result TestShellCmdResult
|
||||||
if project.IsBuilt {
|
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 {
|
} else {
|
||||||
result = knFunc.Exec("deploy", "--path", project.ProjectPath, "--registry", GetRegistry())
|
result = knFunc.Exec("deploy", "--path", project.ProjectPath, "--registry", GetRegistry())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,19 @@
|
||||||
package oncluster
|
package oncluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
fn "knative.dev/kn-plugin-func"
|
||||||
common "knative.dev/kn-plugin-func/test/_common"
|
common "knative.dev/kn-plugin-func/test/_common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Git struct {
|
// UpdateFuncGit updates a function's git settings
|
||||||
URL string
|
func UpdateFuncGit(t *testing.T, projectDir string, git fn.Git) {
|
||||||
Revision string
|
f, err := fn.NewFunction(projectDir)
|
||||||
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)
|
|
||||||
AssertNoError(t, err)
|
AssertNoError(t, err)
|
||||||
|
f.Git = git
|
||||||
m := make(map[interface{}]interface{})
|
err = f.Write()
|
||||||
err = yaml.Unmarshal([]byte(data), &m)
|
|
||||||
AssertNoError(t, err)
|
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
|
// 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)
|
sh := GitInitialCommitAndPush(t, funcPath, remoteRepo.ExternalCloneURL)
|
||||||
|
|
||||||
// Update func.yaml build as git + url + context-dir
|
|
||||||
UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL})
|
|
||||||
|
|
||||||
// Deploy it
|
// 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)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
||||||
// Assert "first revision" is returned
|
// Assert "first revision" is returned
|
||||||
|
|
@ -56,7 +58,10 @@ func TestBasicDefault(t *testing.T) {
|
||||||
sh.Exec(`git push`)
|
sh.Exec(`git push`)
|
||||||
|
|
||||||
// Re-Deploy Func
|
// 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
|
e2e.NewRevisionCheck(t, previousServiceRevision, funcName) // Wait New Service Revision
|
||||||
|
|
||||||
// -- Assertions --
|
// -- Assertions --
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,13 @@ func TestContextDirFunc(t *testing.T) {
|
||||||
// Initial commit to repository: git init + commit + push
|
// Initial commit to repository: git init + commit + push
|
||||||
GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL)
|
GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL)
|
||||||
|
|
||||||
// Update func.yaml build as git + url + context-dir
|
knFunc.Exec("deploy",
|
||||||
UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL, ContextDir: funcContextDir})
|
"-p", funcPath,
|
||||||
|
"-r", e2e.GetRegistry(),
|
||||||
knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath)
|
"--remote",
|
||||||
|
"--git-url", remoteRepo.ClusterCloneURL,
|
||||||
|
"--git-dir", funcContextDir,
|
||||||
|
)
|
||||||
defer knFunc.Exec("delete", "-p", funcPath)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
||||||
// -- Assertions --
|
// -- Assertions --
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
fn "knative.dev/kn-plugin-func"
|
||||||
common "knative.dev/kn-plugin-func/test/_common"
|
common "knative.dev/kn-plugin-func/test/_common"
|
||||||
e2e "knative.dev/kn-plugin-func/test/_e2e"
|
e2e "knative.dev/kn-plugin-func/test/_e2e"
|
||||||
)
|
)
|
||||||
|
|
@ -24,9 +25,13 @@ func TestFromCliBuildLocal(t *testing.T) {
|
||||||
defer os.RemoveAll(funcPath)
|
defer os.RemoveAll(funcPath)
|
||||||
|
|
||||||
// Update func.yaml build as local + some fake url (it should not call it anyway)
|
// 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)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
||||||
// -- Assertions --
|
// -- Assertions --
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,16 @@
|
||||||
package oncluster
|
package oncluster
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Tests on this file covers the scenarios when func.yaml is not modified (build: local)
|
Tests on this file covers the following scenarios:
|
||||||
and git build strategy is specified thru CLI.
|
|
||||||
|
|
||||||
A) Default Branch Test
|
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
|
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
|
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 (
|
import (
|
||||||
|
|
@ -50,7 +49,7 @@ func TestFromCliDefaultBranch(t *testing.T) {
|
||||||
knFunc.Exec("deploy",
|
knFunc.Exec("deploy",
|
||||||
"-r", e2e.GetRegistry(),
|
"-r", e2e.GetRegistry(),
|
||||||
"-p", funcPath,
|
"-p", funcPath,
|
||||||
"--build", "git",
|
"--remote",
|
||||||
"--git-url", remoteRepo.ClusterCloneURL)
|
"--git-url", remoteRepo.ClusterCloneURL)
|
||||||
|
|
||||||
defer knFunc.Exec("delete", "-p", funcPath)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
@ -91,7 +90,7 @@ func TestFromCliFeatureBranch(t *testing.T) {
|
||||||
knFunc.Exec("deploy",
|
knFunc.Exec("deploy",
|
||||||
"-r", e2e.GetRegistry(),
|
"-r", e2e.GetRegistry(),
|
||||||
"-p", funcPath,
|
"-p", funcPath,
|
||||||
"--build", "git",
|
"--remote",
|
||||||
"--git-url", remoteRepo.ClusterCloneURL,
|
"--git-url", remoteRepo.ClusterCloneURL,
|
||||||
"--git-branch", "feature/branch")
|
"--git-branch", "feature/branch")
|
||||||
|
|
||||||
|
|
@ -129,7 +128,7 @@ func TestFromCliContextDirFunc(t *testing.T) {
|
||||||
knFunc.Exec("deploy",
|
knFunc.Exec("deploy",
|
||||||
"-r", e2e.GetRegistry(),
|
"-r", e2e.GetRegistry(),
|
||||||
"-p", funcPath,
|
"-p", funcPath,
|
||||||
"--build", "git",
|
"--remote",
|
||||||
"--git-url", remoteRepo.ClusterCloneURL,
|
"--git-url", remoteRepo.ClusterCloneURL,
|
||||||
"--git-dir", funcContextDir)
|
"--git-dir", funcContextDir)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
fn "knative.dev/kn-plugin-func"
|
||||||
common "knative.dev/kn-plugin-func/test/_common"
|
common "knative.dev/kn-plugin-func/test/_common"
|
||||||
e2e "knative.dev/kn-plugin-func/test/_e2e"
|
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 add index.js")
|
||||||
sh.Exec(`git commit -m "feature branch change"`)
|
sh.Exec(`git commit -m "feature branch change"`)
|
||||||
sh.Exec("git push -u origin feature/branch")
|
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 {
|
assertBodyFn := func(response string) bool {
|
||||||
|
|
@ -54,7 +55,7 @@ func TestFromRevisionTag(t *testing.T) {
|
||||||
sh.Exec("git add index.js")
|
sh.Exec("git add index.js")
|
||||||
sh.Exec(`git commit -m "version 2"`)
|
sh.Exec(`git commit -m "version 2"`)
|
||||||
sh.Exec("git push origin main")
|
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 {
|
assertBodyFn := func(response string) bool {
|
||||||
|
|
@ -77,7 +78,7 @@ func TestFromCommitHash(t *testing.T) {
|
||||||
sh.Exec(`git commit -m "version 2"`)
|
sh.Exec(`git commit -m "version 2"`)
|
||||||
sh.Exec("git push origin main")
|
sh.Exec("git push origin main")
|
||||||
commitHash := strings.TrimSpace(gitRevParse.Stdout)
|
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)
|
t.Logf("Revision Check: commit hash resolved to [%v]", commitHash)
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +110,10 @@ func GitRevisionCheck(
|
||||||
// Setup specific code
|
// Setup specific code
|
||||||
setupCodeFn(sh, funcPath, remoteRepo.ClusterCloneURL)
|
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)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
||||||
// -- Assertions --
|
// -- Assertions --
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func runtimeImpl(t *testing.T, lang string) {
|
||||||
knFunc.Exec("deploy",
|
knFunc.Exec("deploy",
|
||||||
"-r", e2e.GetRegistry(),
|
"-r", e2e.GetRegistry(),
|
||||||
"-p", funcPath,
|
"-p", funcPath,
|
||||||
"--build", "git",
|
"--remote",
|
||||||
"--git-url", remoteRepo.ClusterCloneURL)
|
"--git-url", remoteRepo.ClusterCloneURL)
|
||||||
|
|
||||||
defer knFunc.Exec("delete", "-p", funcPath)
|
defer knFunc.Exec("delete", "-p", funcPath)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue