From c7d4edba85483f382cdf2ba0c96cd48b8f889e38 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Wed, 19 Feb 2020 13:26:24 -0500 Subject: [PATCH] Simplify Debug Transformer interface and allow Apply to fail on images - avoid panics by returning a ContainerDebugConfiguration instead of a pointer - allow returning a different debug-helper image - return a more helpful error message --- pkg/skaffold/debug/debug_test.go | 8 +- pkg/skaffold/debug/transform.go | 21 +- pkg/skaffold/debug/transform_go.go | 15 +- pkg/skaffold/debug/transform_go_test.go | 24 +- pkg/skaffold/debug/transform_jvm.go | 11 +- pkg/skaffold/debug/transform_jvm_test.go | 16 +- pkg/skaffold/debug/transform_nodejs.go | 11 +- pkg/skaffold/debug/transform_nodejs_test.go | 22 +- pkg/skaffold/debug/transform_python.go | 14 +- pkg/skaffold/debug/transform_python_test.go | 21 +- pkg/skaffold/schema/v2beta1/config.go | 868 ++++++++++++++++++++ pkg/skaffold/schema/v2beta1/upgrade.go | 28 + 12 files changed, 985 insertions(+), 74 deletions(-) create mode 100755 pkg/skaffold/schema/v2beta1/config.go create mode 100755 pkg/skaffold/schema/v2beta1/upgrade.go diff --git a/pkg/skaffold/debug/debug_test.go b/pkg/skaffold/debug/debug_test.go index c2b09c52e52..833faef2daf 100644 --- a/pkg/skaffold/debug/debug_test.go +++ b/pkg/skaffold/debug/debug_test.go @@ -111,18 +111,14 @@ func (t testTransformer) IsApplicable(config imageConfiguration) bool { return true } -func (t testTransformer) RuntimeSupportImage() string { - return "" -} - -func (t testTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration { +func (t testTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { port := portAlloc(9999) container.Ports = append(container.Ports, v1.ContainerPort{Name: "test", ContainerPort: port}) testEnv := v1.EnvVar{Name: "KEY", Value: "value"} container.Env = append(container.Env, testEnv) - return &ContainerDebugConfiguration{Runtime: "test"} + return ContainerDebugConfiguration{Runtime: "test"}, "", nil } func TestApplyDebuggingTransforms(t *testing.T) { diff --git a/pkg/skaffold/debug/transform.go b/pkg/skaffold/debug/transform.go index 756155b61a4..d8c2355b977 100644 --- a/pkg/skaffold/debug/transform.go +++ b/pkg/skaffold/debug/transform.go @@ -102,11 +102,11 @@ type containerTransformer interface { // IsApplicable determines if this container is suitable to be transformed. IsApplicable(config imageConfiguration) bool - // RuntimeSupportImage returns the associated duct-tape helper image required or empty string - RuntimeSupportImage() string - - // Apply configures a container definition for debugging, returning the debug configuration details or `nil` if it could not be done - Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration + // Apply configures a container definition for debugging, returning the debug configuration details + // and required initContainer (an empty string if not required), or return a non-nil error if + // the container could not be transformed. The initContainer image is intended to install any + // required debug support tools. + Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) } const ( @@ -195,10 +195,11 @@ func transformPodSpec(metadata *metav1.ObjectMeta, podSpec *v1.PodSpec, retrieve continue } // requiredImage, if not empty, is the image ID providing the debugging support files + // `err != nil` means that the container did not or could not be transformed if configuration, requiredImage, err := transformContainer(container, imageConfig, portAlloc); err == nil { configuration.Artifact = imageConfig.artifact configuration.WorkingDir = imageConfig.workingDir - configurations[container.Name] = *configuration + configurations[container.Name] = configuration if len(requiredImage) > 0 { logrus.Infof("%q requires debugging support image %q", container.Name, requiredImage) containersRequiringSupport = append(containersRequiringSupport, container) @@ -206,7 +207,7 @@ func transformPodSpec(metadata *metav1.ObjectMeta, podSpec *v1.PodSpec, retrieve } // todo: add this artifact to the watch list? } else { - logrus.Infof("Image %q not configured for debugging: %v", container.Name, err) + logrus.Warnf("Image %q not configured for debugging: %v", container.Name, err) } } @@ -282,7 +283,7 @@ func isPortAvailable(podSpec *v1.PodSpec, port int32) bool { // transformContainer rewrites the container definition to enable debugging. // Returns a debugging configuration description with associated language runtime support // container image, or an error if the rewrite was unsuccessful. -func transformContainer(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (*ContainerDebugConfiguration, string, error) { +func transformContainer(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { // update image configuration values with those set in the k8s manifest for _, envVar := range container.Env { // FIXME handle ValueFrom? @@ -301,10 +302,10 @@ func transformContainer(container *v1.Container, config imageConfiguration, port for _, transform := range containerTransforms { if transform.IsApplicable(config) { - return transform.Apply(container, config, portAlloc), transform.RuntimeSupportImage(), nil + return transform.Apply(container, config, portAlloc) } } - return nil, "", fmt.Errorf("unable to determine runtime for %q", container.Name) + return ContainerDebugConfiguration{}, "", fmt.Errorf("unable to determine runtime for %q", container.Name) } func encodeConfigurations(configurations map[string]ContainerDebugConfiguration) string { diff --git a/pkg/skaffold/debug/transform_go.go b/pkg/skaffold/debug/transform_go.go index f90662fbb13..f183d790a11 100644 --- a/pkg/skaffold/debug/transform_go.go +++ b/pkg/skaffold/debug/transform_go.go @@ -73,13 +73,9 @@ func (t dlvTransformer) IsApplicable(config imageConfiguration) bool { return false } -func (t dlvTransformer) RuntimeSupportImage() string { - return "go" -} - // Apply configures a container definition for Go with Delve. -// Returns a simple map describing the debug configuration details. -func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration { +// Returns the debug configuration details, with the "go" support image +func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { logrus.Infof("Configuring %q for Go/Delve debugging", container.Name) // try to find existing `dlv` command @@ -96,17 +92,16 @@ func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration container.Args = rewriteDlvCommandLine(config.arguments, *spec, container.Args) default: - logrus.Warnf("Skipping %q as does not appear to be Go-based", container.Name) - return nil + return ContainerDebugConfiguration{}, "", fmt.Errorf("container %q has no command-line", container.Name) } } container.Ports = exposePort(container.Ports, "dlv", int32(spec.port)) - return &ContainerDebugConfiguration{ + return ContainerDebugConfiguration{ Runtime: "go", Ports: map[string]uint32{"dlv": uint32(spec.port)}, - } + }, "go", nil } func retrieveDlvSpec(config imageConfiguration) *dlvSpec { diff --git a/pkg/skaffold/debug/transform_go_test.go b/pkg/skaffold/debug/transform_go_test.go index 534ce12bfd8..be2126bff43 100644 --- a/pkg/skaffold/debug/transform_go_test.go +++ b/pkg/skaffold/debug/transform_go_test.go @@ -114,22 +114,21 @@ func TestDlvTransformer_IsApplicable(t *testing.T) { } } -func TestDlvTransformer_RuntimeSupportImage(t *testing.T) { - testutil.CheckDeepEqual(t, "go", dlvTransformer{}.RuntimeSupportImage()) -} - func TestDlvTransformerApply(t *testing.T) { tests := []struct { description string containerSpec v1.Container configuration imageConfiguration + shouldErr bool result v1.Container + debugConfig ContainerDebugConfiguration + image string }{ { description: "empty", containerSpec: v1.Container{}, configuration: imageConfiguration{}, - result: v1.Container{}, + shouldErr: true, }, { description: "basic", @@ -139,6 +138,8 @@ func TestDlvTransformerApply(t *testing.T) { Command: []string{"/dbg/go/bin/dlv", "exec", "--headless", "--continue", "--accept-multiclient", "--listen=localhost:56268", "--api-version=2", "app", "--", "arg"}, Ports: []v1.ContainerPort{{Name: "dlv", ContainerPort: 56268}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "go", Ports: map[string]uint32{"dlv": 56268}}, + image: "go", }, { description: "existing port", @@ -150,6 +151,8 @@ func TestDlvTransformerApply(t *testing.T) { Command: []string{"/dbg/go/bin/dlv", "exec", "--headless", "--continue", "--accept-multiclient", "--listen=localhost:56268", "--api-version=2", "app", "--", "arg"}, Ports: []v1.ContainerPort{{Name: "http-server", ContainerPort: 8080}, {Name: "dlv", ContainerPort: 56268}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "go", Ports: map[string]uint32{"dlv": 56268}}, + image: "go", }, { description: "existing dlv port", @@ -161,6 +164,8 @@ func TestDlvTransformerApply(t *testing.T) { Command: []string{"/dbg/go/bin/dlv", "exec", "--headless", "--continue", "--accept-multiclient", "--listen=localhost:56268", "--api-version=2", "app", "--", "arg"}, Ports: []v1.ContainerPort{{Name: "dlv", ContainerPort: 56268}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "go", Ports: map[string]uint32{"dlv": 56268}}, + image: "go", }, { description: "command not entrypoint", @@ -170,6 +175,8 @@ func TestDlvTransformerApply(t *testing.T) { Args: []string{"/dbg/go/bin/dlv", "exec", "--headless", "--continue", "--accept-multiclient", "--listen=localhost:56268", "--api-version=2", "app", "--", "arg"}, Ports: []v1.ContainerPort{{Name: "dlv", ContainerPort: 56268}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "go", Ports: map[string]uint32{"dlv": 56268}}, + image: "go", }, { description: "entrypoint with args in container spec", @@ -182,6 +189,8 @@ func TestDlvTransformerApply(t *testing.T) { Args: []string{"arg1", "arg2"}, Ports: []v1.ContainerPort{{Name: "dlv", ContainerPort: 56268}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "go", Ports: map[string]uint32{"dlv": 56268}}, + image: "go", }, } var identity portAllocator = func(port int32) int32 { @@ -189,9 +198,12 @@ func TestDlvTransformerApply(t *testing.T) { } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { - dlvTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + config, image, err := dlvTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + t.CheckError(test.shouldErr, err) t.CheckDeepEqual(test.result, test.containerSpec) + t.CheckDeepEqual(test.debugConfig, config) + t.CheckDeepEqual(test.image, image) }) } } diff --git a/pkg/skaffold/debug/transform_jvm.go b/pkg/skaffold/debug/transform_jvm.go index ee49c14731e..0e230fbfab1 100644 --- a/pkg/skaffold/debug/transform_jvm.go +++ b/pkg/skaffold/debug/transform_jvm.go @@ -63,14 +63,9 @@ type jdwpSpec struct { server bool } -func (t jdwpTransformer) RuntimeSupportImage() string { - // no additional support required - return "" -} - // Apply configures a container definition for JVM debugging. // Returns a simple map describing the debug configuration details. -func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration { +func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { logrus.Infof("Configuring %q for JVM debugging", container.Name) // try to find existing JAVA_TOOL_OPTIONS or jdwp command argument spec := retrieveJdwpSpec(config) @@ -89,10 +84,10 @@ func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguratio container.Ports = exposePort(container.Ports, "jdwp", port) - return &ContainerDebugConfiguration{ + return ContainerDebugConfiguration{ Runtime: "jvm", Ports: map[string]uint32{"jdwp": uint32(port)}, - } + }, "", nil } func retrieveJdwpSpec(config imageConfiguration) *jdwpSpec { diff --git a/pkg/skaffold/debug/transform_jvm_test.go b/pkg/skaffold/debug/transform_jvm_test.go index 88f6bd8fd44..99f70093409 100644 --- a/pkg/skaffold/debug/transform_jvm_test.go +++ b/pkg/skaffold/debug/transform_jvm_test.go @@ -29,10 +29,6 @@ import ( "github.com/GoogleContainerTools/skaffold/testutil" ) -func TestJdwpTransformer_RuntimeSupportImage(t *testing.T) { - testutil.CheckDeepEqual(t, "", jdwpTransformer{}.RuntimeSupportImage()) -} - func TestJdwpTransformer_IsApplicable(t *testing.T) { tests := []struct { description string @@ -95,6 +91,8 @@ func TestJdwpTransformerApply(t *testing.T) { containerSpec v1.Container configuration imageConfiguration result v1.Container + debugConfig ContainerDebugConfiguration + image string }{ { description: "empty", @@ -104,6 +102,7 @@ func TestJdwpTransformerApply(t *testing.T) { Env: []v1.EnvVar{{Name: "JAVA_TOOL_OPTIONS", Value: "-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y"}}, Ports: []v1.ContainerPort{{Name: "jdwp", ContainerPort: 5005}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "jvm", Ports: map[string]uint32{"jdwp": 5005}}, }, { description: "existing port", @@ -115,6 +114,7 @@ func TestJdwpTransformerApply(t *testing.T) { Env: []v1.EnvVar{{Name: "JAVA_TOOL_OPTIONS", Value: "-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y"}}, Ports: []v1.ContainerPort{{Name: "http-server", ContainerPort: 8080}, {Name: "jdwp", ContainerPort: 5005}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "jvm", Ports: map[string]uint32{"jdwp": 5005}}, }, { description: "existing jdwp spec", @@ -127,6 +127,7 @@ func TestJdwpTransformerApply(t *testing.T) { Env: []v1.EnvVar{{Name: "JAVA_TOOL_OPTIONS", Value: "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n,quiet=y"}}, Ports: []v1.ContainerPort{{ContainerPort: 5005}, {Name: "jdwp", ContainerPort: 8000}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "jvm", Ports: map[string]uint32{"jdwp": 8000}}, }, { description: "existing jdwp port and JAVA_TOOL_OPTIONS", @@ -139,6 +140,7 @@ func TestJdwpTransformerApply(t *testing.T) { Env: []v1.EnvVar{{Name: "FOO", Value: "BAR"}, {Name: "JAVA_TOOL_OPTIONS", Value: "-Xms1g -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y"}}, Ports: []v1.ContainerPort{{Name: "jdwp", ContainerPort: 5005}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "jvm", Ports: map[string]uint32{"jdwp": 5005}}, }, } var identity portAllocator = func(port int32) int32 { @@ -146,9 +148,13 @@ func TestJdwpTransformerApply(t *testing.T) { } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { - jdwpTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + config, image, err := jdwpTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + // Apply never fails since there's always the option to set JAVA_TOOL_OPTIONS + t.CheckNil(err) t.CheckDeepEqual(test.result, test.containerSpec) + t.CheckDeepEqual(test.debugConfig, config) + t.CheckDeepEqual(test.image, image) }) } } diff --git a/pkg/skaffold/debug/transform_nodejs.go b/pkg/skaffold/debug/transform_nodejs.go index e3e7b3a83ab..f312db880c9 100644 --- a/pkg/skaffold/debug/transform_nodejs.go +++ b/pkg/skaffold/debug/transform_nodejs.go @@ -67,14 +67,9 @@ func (t nodeTransformer) IsApplicable(config imageConfiguration) bool { return false } -func (t nodeTransformer) RuntimeSupportImage() string { - // no additional support required - return "" -} - // Apply configures a container definition for NodeJS Chrome V8 Inspector. // Returns a simple map describing the debug configuration details. -func (t nodeTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration { +func (t nodeTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { logrus.Infof("Configuring %q for node.js debugging", container.Name) // try to find existing `--inspect` command @@ -102,10 +97,10 @@ func (t nodeTransformer) Apply(container *v1.Container, config imageConfiguratio container.Ports = exposePort(container.Ports, "devtools", spec.port) - return &ContainerDebugConfiguration{ + return ContainerDebugConfiguration{ Runtime: "nodejs", Ports: map[string]uint32{"devtools": uint32(spec.port)}, - } + }, "", nil } func retrieveNodeInspectSpec(config imageConfiguration) *inspectSpec { diff --git a/pkg/skaffold/debug/transform_nodejs_test.go b/pkg/skaffold/debug/transform_nodejs_test.go index fecd4ca3aa7..7ce4094d74c 100644 --- a/pkg/skaffold/debug/transform_nodejs_test.go +++ b/pkg/skaffold/debug/transform_nodejs_test.go @@ -60,10 +60,6 @@ func TestExtractInspectArg(t *testing.T) { } } -func TestNodeTransformer_RuntimeSupportImage(t *testing.T) { - testutil.CheckDeepEqual(t, "", nodeTransformer{}.RuntimeSupportImage()) -} - func TestNodeTransformer_IsApplicable(t *testing.T) { tests := []struct { description string @@ -195,21 +191,24 @@ func TestRewriteNpmCommandLine(t *testing.T) { } func TestNodeTransformer_Apply(t *testing.T) { + // no shouldErr as Apply always succeeds tests := []struct { description string containerSpec v1.Container configuration imageConfiguration result v1.Container + debugConfig ContainerDebugConfiguration + image string }{ { description: "empty", containerSpec: v1.Container{}, configuration: imageConfiguration{}, - // since IsApply() presumably returned true, this is the default case result: v1.Container{ - Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, Env: []v1.EnvVar{{Name: "NODE_OPTIONS", Value: "--inspect=9229"}}, + Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, { description: "entrypoint", @@ -219,6 +218,7 @@ func TestNodeTransformer_Apply(t *testing.T) { Command: []string{"node", "--inspect=9229"}, Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, { description: "existing port", @@ -230,6 +230,7 @@ func TestNodeTransformer_Apply(t *testing.T) { Command: []string{"node", "--inspect=9229"}, Ports: []v1.ContainerPort{{Name: "http-server", ContainerPort: 8080}, {Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, { description: "existing devtools port", @@ -241,6 +242,7 @@ func TestNodeTransformer_Apply(t *testing.T) { Command: []string{"node", "--inspect=9229"}, Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, { description: "command not entrypoint", @@ -250,6 +252,7 @@ func TestNodeTransformer_Apply(t *testing.T) { Args: []string{"node", "--inspect=9229"}, Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, { description: "docker-entrypoint (#3821)", @@ -262,6 +265,7 @@ func TestNodeTransformer_Apply(t *testing.T) { Env: []v1.EnvVar{{Name: "NODE_VERSION", Value: "10.12"}, {Name: "NODE_OPTIONS", Value: "--inspect=9229"}}, Ports: []v1.ContainerPort{{Name: "devtools", ContainerPort: 9229}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "nodejs", Ports: map[string]uint32{"devtools": 9229}}, }, } var identity portAllocator = func(port int32) int32 { @@ -269,9 +273,13 @@ func TestNodeTransformer_Apply(t *testing.T) { } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { - nodeTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + config, image, err := nodeTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + // Apply never fails since there's always the option to set NODE_OPTIONS + t.CheckNil(err) t.CheckDeepEqual(test.result, test.containerSpec) + t.CheckDeepEqual(test.debugConfig, config) + t.CheckDeepEqual(test.image, image) // always empty as we don't have a nodejs image }) } } diff --git a/pkg/skaffold/debug/transform_python.go b/pkg/skaffold/debug/transform_python.go index 2c5ba446d3d..80dacf7875b 100644 --- a/pkg/skaffold/debug/transform_python.go +++ b/pkg/skaffold/debug/transform_python.go @@ -17,6 +17,7 @@ limitations under the License. package debug import ( + "fmt" "strconv" "strings" @@ -62,13 +63,9 @@ func (t pythonTransformer) IsApplicable(config imageConfiguration) bool { return false } -func (t pythonTransformer) RuntimeSupportImage() string { - return "python" -} - // Apply configures a container definition for Python with pydev/ptvsd // Returns a simple map describing the debug configuration details. -func (t pythonTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration { +func (t pythonTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) { logrus.Infof("Configuring %q for python debugging", container.Name) // try to find existing `-mptvsd` command @@ -84,8 +81,7 @@ func (t pythonTransformer) Apply(container *v1.Container, config imageConfigurat container.Args = rewritePythonCommandLine(config.arguments, *spec) default: - logrus.Warnf("Skipping %q as does not appear to invoke python", container.Name) - return nil + return ContainerDebugConfiguration{}, "", fmt.Errorf("%q does not appear to invoke python", container.Name) } } @@ -97,10 +93,10 @@ func (t pythonTransformer) Apply(container *v1.Container, config imageConfigurat container.Env = setEnvVar(container.Env, "PYTHONUSERBASE", pyUserBase) container.Ports = exposePort(container.Ports, "dap", spec.port) - return &ContainerDebugConfiguration{ + return ContainerDebugConfiguration{ Runtime: "python", Ports: map[string]uint32{"dap": uint32(spec.port)}, - } + }, "python", nil } func retrievePtvsdSpec(config imageConfiguration) *ptvsdSpec { diff --git a/pkg/skaffold/debug/transform_python_test.go b/pkg/skaffold/debug/transform_python_test.go index 2a80fe4c96f..74b5be7677c 100644 --- a/pkg/skaffold/debug/transform_python_test.go +++ b/pkg/skaffold/debug/transform_python_test.go @@ -129,22 +129,22 @@ func TestPythonTransformer_IsApplicable(t *testing.T) { } } -func TestPythonTransformer_RuntimeSupportImage(t *testing.T) { - testutil.CheckDeepEqual(t, "python", pythonTransformer{}.RuntimeSupportImage()) -} - func TestPythonTransformer_Apply(t *testing.T) { tests := []struct { description string containerSpec v1.Container configuration imageConfiguration + shouldErr bool result v1.Container + debugConfig ContainerDebugConfiguration + image string }{ { description: "empty", containerSpec: v1.Container{}, configuration: imageConfiguration{}, result: v1.Container{}, + shouldErr: true, }, { description: "basic", @@ -155,6 +155,8 @@ func TestPythonTransformer_Apply(t *testing.T) { Ports: []v1.ContainerPort{{Name: "dap", ContainerPort: 5678}}, Env: []v1.EnvVar{{Name: "PYTHONUSERBASE", Value: "/dbg/python"}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "python", Ports: map[string]uint32{"dap": 5678}}, + image: "python", }, { description: "existing port", @@ -167,6 +169,8 @@ func TestPythonTransformer_Apply(t *testing.T) { Ports: []v1.ContainerPort{{Name: "http-server", ContainerPort: 8080}, {Name: "dap", ContainerPort: 5678}}, Env: []v1.EnvVar{{Name: "PYTHONUSERBASE", Value: "/dbg/python"}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "python", Ports: map[string]uint32{"dap": 5678}}, + image: "python", }, { description: "existing port and env", @@ -179,6 +183,8 @@ func TestPythonTransformer_Apply(t *testing.T) { Ports: []v1.ContainerPort{{Name: "dap", ContainerPort: 5678}}, Env: []v1.EnvVar{{Name: "PYTHONUSERBASE", Value: "/dbg/python:/foo"}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "python", Ports: map[string]uint32{"dap": 5678}}, + image: "python", }, { description: "command not entrypoint", @@ -189,6 +195,8 @@ func TestPythonTransformer_Apply(t *testing.T) { Ports: []v1.ContainerPort{{Name: "dap", ContainerPort: 5678}}, Env: []v1.EnvVar{{Name: "PYTHONUSERBASE", Value: "/dbg/python"}}, }, + debugConfig: ContainerDebugConfiguration{Runtime: "python", Ports: map[string]uint32{"dap": 5678}}, + image: "python", }, } var identity portAllocator = func(port int32) int32 { @@ -196,9 +204,12 @@ func TestPythonTransformer_Apply(t *testing.T) { } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { - pythonTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + config, image, err := pythonTransformer{}.Apply(&test.containerSpec, test.configuration, identity) + t.CheckError(test.shouldErr, err) t.CheckDeepEqual(test.result, test.containerSpec) + t.CheckDeepEqual(test.debugConfig, config) + t.CheckDeepEqual(test.image, image) }) } } diff --git a/pkg/skaffold/schema/v2beta1/config.go b/pkg/skaffold/schema/v2beta1/config.go new file mode 100755 index 00000000000..413f1f437b1 --- /dev/null +++ b/pkg/skaffold/schema/v2beta1/config.go @@ -0,0 +1,868 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2beta1 + +import ( + v1 "k8s.io/api/core/v1" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" +) + +// !!! WARNING !!! This config version is already released, please DO NOT MODIFY the structs in this file. +const Version string = "skaffold/v2beta1" + +// NewSkaffoldConfig creates a SkaffoldConfig +func NewSkaffoldConfig() util.VersionedConfig { + return new(SkaffoldConfig) +} + +// SkaffoldConfig holds the fields parsed from the Skaffold configuration file (skaffold.yaml). +type SkaffoldConfig struct { + // APIVersion is the version of the configuration. + APIVersion string `yaml:"apiVersion" yamltags:"required"` + + // Kind is always `Config`. Defaults to `Config`. + Kind string `yaml:"kind" yamltags:"required"` + + // Metadata holds additional information about the config. + Metadata Metadata `yaml:"metadata,omitempty"` + + // Pipeline defines the Build/Test/Deploy phases. + Pipeline `yaml:",inline"` + + // Profiles *beta* can override be used to `build`, `test` or `deploy` configuration. + Profiles []Profile `yaml:"profiles,omitempty"` +} + +// Metadata holds an optional name of the project. +type Metadata struct { + // Name is an identifier for the project. + Name string `yaml:"name,omitempty"` +} + +// Pipeline describes a Skaffold pipeline. +type Pipeline struct { + // Build describes how images are built. + Build BuildConfig `yaml:"build,omitempty"` + + // Test describes how images are tested. + Test []*TestCase `yaml:"test,omitempty"` + + // Deploy describes how images are deployed. + Deploy DeployConfig `yaml:"deploy,omitempty"` + + // PortForward describes user defined resources to port-forward. + PortForward []*PortForwardResource `yaml:"portForward,omitempty"` +} + +func (c *SkaffoldConfig) GetVersion() string { + return c.APIVersion +} + +// ResourceType describes the Kubernetes resource types used for port forwarding. +type ResourceType string + +// PortForwardResource describes a resource to port forward. +type PortForwardResource struct { + // Type is the Kubernetes type that should be port forwarded. + // Acceptable resource types include: `Service`, `Pod` and Controller resource type that has a pod spec: `ReplicaSet`, `ReplicationController`, `Deployment`, `StatefulSet`, `DaemonSet`, `Job`, `CronJob`. + Type ResourceType `yaml:"resourceType,omitempty"` + + // Name is the name of the Kubernetes resource to port forward. + Name string `yaml:"resourceName,omitempty"` + + // Namespace is the namespace of the resource to port forward. + Namespace string `yaml:"namespace,omitempty"` + + // Port is the resource port that will be forwarded. + Port int `yaml:"port,omitempty"` + + // Address is the local address to bind to. Defaults to the loopback address 127.0.0.1. + Address string `yaml:"address,omitempty"` + + // LocalPort is the local port to forward to. If the port is unavailable, Skaffold will choose a random open port to forward to. *Optional*. + LocalPort int `yaml:"localPort,omitempty"` +} + +// BuildConfig contains all the configuration for the build steps. +type BuildConfig struct { + // Artifacts lists the images you're going to be building. + Artifacts []*Artifact `yaml:"artifacts,omitempty"` + + // InsecureRegistries is a list of registries declared by the user to be insecure. + // These registries will be connected to via HTTP instead of HTTPS. + InsecureRegistries []string `yaml:"insecureRegistries,omitempty"` + + // TagPolicy *beta* determines how images are tagged. + // A few strategies are provided here, although you most likely won't need to care! + // If not specified, it defaults to `gitCommit: {variant: Tags}`. + TagPolicy TagPolicy `yaml:"tagPolicy,omitempty"` + + BuildType `yaml:",inline"` +} + +// TagPolicy contains all the configuration for the tagging step. +type TagPolicy struct { + // GitTagger *beta* tags images with the git tag or commit of the artifact's workspace. + GitTagger *GitTagger `yaml:"gitCommit,omitempty" yamltags:"oneOf=tag"` + + // ShaTagger *beta* tags images with their sha256 digest. + ShaTagger *ShaTagger `yaml:"sha256,omitempty" yamltags:"oneOf=tag"` + + // EnvTemplateTagger *beta* tags images with a configurable template string. + EnvTemplateTagger *EnvTemplateTagger `yaml:"envTemplate,omitempty" yamltags:"oneOf=tag"` + + // DateTimeTagger *beta* tags images with the build timestamp. + DateTimeTagger *DateTimeTagger `yaml:"dateTime,omitempty" yamltags:"oneOf=tag"` +} + +// ShaTagger *beta* tags images with their sha256 digest. +type ShaTagger struct{} + +// GitTagger *beta* tags images with the git tag or commit of the artifact's workspace. +type GitTagger struct { + // Variant determines the behavior of the git tagger. Valid variants are + // `Tags` (default): use git tags or fall back to abbreviated commit hash. + // `CommitSha`: use the full git commit sha. + // `AbbrevCommitSha`: use the abbreviated git commit sha. + // `TreeSha`: use the full tree hash of the artifact workingdir. + // `AbbrevTreeSha`: use the abbreviated tree hash of the artifact workingdir. + Variant string `yaml:"variant,omitempty"` +} + +// EnvTemplateTagger *beta* tags images with a configurable template string. +type EnvTemplateTagger struct { + // Template used to produce the image name and tag. + // See golang [text/template](https://golang.org/pkg/text/template/). + // The template is executed against the current environment, + // with those variables injected: + // IMAGE_NAME | Name of the image being built, as supplied in the artifacts section. + // For example: `{{.RELEASE}}-{{.IMAGE_NAME}}`. + Template string `yaml:"template,omitempty" yamltags:"required"` +} + +// DateTimeTagger *beta* tags images with the build timestamp. +type DateTimeTagger struct { + // Format formats the date and time. + // See [#Time.Format](https://golang.org/pkg/time/#Time.Format). + // Defaults to `2006-01-02_15-04-05.999_MST`. + Format string `yaml:"format,omitempty"` + + // TimeZone sets the timezone for the date and time. + // See [Time.LoadLocation](https://golang.org/pkg/time/#Time.LoadLocation). + // Defaults to the local timezone. + TimeZone string `yaml:"timezone,omitempty"` +} + +// BuildType contains the specific implementation and parameters needed +// for the build step. Only one field should be populated. +type BuildType struct { + // LocalBuild *beta* describes how to do a build on the local docker daemon + // and optionally push to a repository. + LocalBuild *LocalBuild `yaml:"local,omitempty" yamltags:"oneOf=build"` + + // GoogleCloudBuild *beta* describes how to do a remote build on + // [Google Cloud Build](https://cloud.google.com/cloud-build/). + GoogleCloudBuild *GoogleCloudBuild `yaml:"googleCloudBuild,omitempty" yamltags:"oneOf=build"` + + // Cluster *beta* describes how to do an on-cluster build. + Cluster *ClusterDetails `yaml:"cluster,omitempty" yamltags:"oneOf=build"` +} + +// LocalBuild *beta* describes how to do a build on the local docker daemon +// and optionally push to a repository. +type LocalBuild struct { + // Push should images be pushed to a registry. + // If not specified, images are pushed only if the current Kubernetes context + // connects to a remote cluster. + Push *bool `yaml:"push,omitempty"` + + // UseDockerCLI use `docker` command-line interface instead of Docker Engine APIs. + UseDockerCLI bool `yaml:"useDockerCLI,omitempty"` + + // UseBuildkit use BuildKit to build Docker images. + UseBuildkit bool `yaml:"useBuildkit,omitempty"` + + // Concurrency is how many artifacts can be built concurrently. 0 means "no-limit". + // Defaults to `1`. + Concurrency *int `yaml:"concurrency,omitempty"` +} + +// GoogleCloudBuild *beta* describes how to do a remote build on +// [Google Cloud Build](https://cloud.google.com/cloud-build/docs/). +// Docker and Jib artifacts can be built on Cloud Build. The `projectId` needs +// to be provided and the currently logged in user should be given permissions to trigger +// new builds. +type GoogleCloudBuild struct { + // ProjectID is the ID of your Cloud Platform Project. + // If it is not provided, Skaffold will guess it from the image name. + // For example, given the artifact image name `gcr.io/myproject/image`, Skaffold + // will use the `myproject` GCP project. + ProjectID string `yaml:"projectId,omitempty"` + + // DiskSizeGb is the disk size of the VM that runs the build. + // See [Cloud Build Reference](https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds#buildoptions). + DiskSizeGb int64 `yaml:"diskSizeGb,omitempty"` + + // MachineType is the type of the VM that runs the build. + // See [Cloud Build Reference](https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds#buildoptions). + MachineType string `yaml:"machineType,omitempty"` + + // Timeout is the amount of time (in seconds) that this build should be allowed to run. + // See [Cloud Build Reference](https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds#resource-build). + Timeout string `yaml:"timeout,omitempty"` + + // DockerImage is the image that runs a Docker build. + // See [Cloud Builders](https://cloud.google.com/cloud-build/docs/cloud-builders). + // Defaults to `gcr.io/cloud-builders/docker`. + DockerImage string `yaml:"dockerImage,omitempty"` + + // KanikoImage is the image that runs a Kaniko build. + // See [Cloud Builders](https://cloud.google.com/cloud-build/docs/cloud-builders). + // Defaults to `gcr.io/kaniko-project/executor`. + KanikoImage string `yaml:"kanikoImage,omitempty"` + + // MavenImage is the image that runs a Maven build. + // See [Cloud Builders](https://cloud.google.com/cloud-build/docs/cloud-builders). + // Defaults to `gcr.io/cloud-builders/mvn`. + MavenImage string `yaml:"mavenImage,omitempty"` + + // GradleImage is the image that runs a Gradle build. + // See [Cloud Builders](https://cloud.google.com/cloud-build/docs/cloud-builders). + // Defaults to `gcr.io/cloud-builders/gradle`. + GradleImage string `yaml:"gradleImage,omitempty"` + + // PackImage is the image that runs a Cloud Native Buildpacks build. + // See [Cloud Builders](https://cloud.google.com/cloud-build/docs/cloud-builders). + // Defaults to `gcr.io/k8s-skaffold/pack`. + PackImage string `yaml:"packImage,omitempty"` + + // Concurrency is how many artifacts can be built concurrently. 0 means "no-limit". + // Defaults to `0`. + Concurrency int `yaml:"concurrency,omitempty"` +} + +// KanikoCache configures Kaniko caching. If a cache is specified, Kaniko will +// use a remote cache which will speed up builds. +type KanikoCache struct { + // Repo is a remote repository to store cached layers. If none is specified, one will be + // inferred from the image name. See [Kaniko Caching](https://github.com/GoogleContainerTools/kaniko#caching). + Repo string `yaml:"repo,omitempty"` + // HostPath specifies a path on the host that is mounted to each pod as read only cache volume containing base images. + // If set, must exist on each node and prepopulated with kaniko-warmer. + HostPath string `yaml:"hostPath,omitempty"` +} + +// ClusterDetails *beta* describes how to do an on-cluster build. +type ClusterDetails struct { + // HTTPProxy for kaniko pod. + HTTPProxy string `yaml:"HTTP_PROXY,omitempty"` + + // HTTPSProxy for kaniko pod. + HTTPSProxy string `yaml:"HTTPS_PROXY,omitempty"` + + // PullSecret is the path to the Google Cloud service account secret key file. + PullSecret string `yaml:"pullSecret,omitempty"` + + // PullSecretName is the name of the Kubernetes secret for pulling base images + // and pushing the final image. If given, the secret needs to contain the Google Cloud + // service account secret key under the key `kaniko-secret`. + // Defaults to `kaniko-secret`. + PullSecretName string `yaml:"pullSecretName,omitempty"` + + // PullSecretMountPath is the path the pull secret will be mounted at within the running container. + PullSecretMountPath string `yaml:"pullSecretMountPath,omitempty"` + + // Namespace is the Kubernetes namespace. + // Defaults to current namespace in Kubernetes configuration. + Namespace string `yaml:"namespace,omitempty"` + + // Timeout is the amount of time (in seconds) that this build is allowed to run. + // Defaults to 20 minutes (`20m`). + Timeout string `yaml:"timeout,omitempty"` + + // DockerConfig describes how to mount the local Docker configuration into a pod. + DockerConfig *DockerConfig `yaml:"dockerConfig,omitempty"` + + // Resources define the resource requirements for the kaniko pod. + Resources *ResourceRequirements `yaml:"resources,omitempty"` + + // Concurrency is how many artifacts can be built concurrently. 0 means "no-limit". + // Defaults to `0`. + Concurrency int `yaml:"concurrency,omitempty"` + + // Volumes defines container mounts for ConfigMap and Secret resources. + Volumes []v1.Volume `yaml:"volumes,omitempty"` + + // RandomPullSecret adds a random UUID postfix to the default name of the pull secret to facilitate parallel builds, e.g. kaniko-secretdocker-cfgfd154022-c761-416f-8eb3-cf8258450b85. + RandomPullSecret bool `yaml:"randomPullSecret,omitempty"` + + // RandomDockerConfigSecret adds a random UUID postfix to the default name of the docker secret to facilitate parallel builds, e.g. docker-cfgfd154022-c761-416f-8eb3-cf8258450b85. + RandomDockerConfigSecret bool `yaml:"randomDockerConfigSecret,omitempty"` +} + +// DockerConfig contains information about the docker `config.json` to mount. +type DockerConfig struct { + // Path is the path to the docker `config.json`. + Path string `yaml:"path,omitempty"` + + // SecretName is the Kubernetes secret that contains the `config.json` Docker configuration. + // Note that the expected secret type is not 'kubernetes.io/dockerconfigjson' but 'Opaque'. + SecretName string `yaml:"secretName,omitempty"` +} + +// ResourceRequirements describes the resource requirements for the kaniko pod. +type ResourceRequirements struct { + // Requests [resource requests](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) for the Kaniko pod. + Requests *ResourceRequirement `yaml:"requests,omitempty"` + + // Limits [resource limits](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) for the Kaniko pod. + Limits *ResourceRequirement `yaml:"limits,omitempty"` +} + +// ResourceRequirement stores the CPU/Memory requirements for the pod. +type ResourceRequirement struct { + // CPU the number cores to be used. + // For example: `2`, `2.0` or `200m`. + CPU string `yaml:"cpu,omitempty"` + + // Memory the amount of memory to allocate to the pod. + // For example: `1Gi` or `1000Mi`. + Memory string `yaml:"memory,omitempty"` + + // EphemeralStorage the amount of Ephemeral storage to allocate to the pod. + // For example: `1Gi` or `1000Mi`. + EphemeralStorage string `yaml:"ephemeralStorage,omitempty"` + + // ResourceStorage the amount of resource storage to allocate to the pod. + // For example: `1Gi` or `1000Mi`. + ResourceStorage string `yaml:"resourceStorage,omitempty"` +} + +// TestCase is a list of structure tests to run on images that Skaffold builds. +type TestCase struct { + // ImageName is the artifact on which to run those tests. + // For example: `gcr.io/k8s-skaffold/example`. + ImageName string `yaml:"image" yamltags:"required"` + + // StructureTests lists the [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) + // to run on that artifact. + // For example: `["./test/*"]`. + StructureTests []string `yaml:"structureTests,omitempty"` +} + +// DeployConfig contains all the configuration needed by the deploy steps. +type DeployConfig struct { + DeployType `yaml:",inline"` + + // StatusCheckDeadlineSeconds *beta* is the deadline for deployments to stabilize in seconds. + StatusCheckDeadlineSeconds int `yaml:"statusCheckDeadlineSeconds,omitempty"` + + // KubeContext is the Kubernetes context that Skaffold should deploy to. + // For example: `minikube`. + KubeContext string `yaml:"kubeContext,omitempty"` +} + +// DeployType contains the specific implementation and parameters needed +// for the deploy step. All three deployer types can be used at the same +// time for hybrid workflows. +type DeployType struct { + // HelmDeploy *beta* uses the `helm` CLI to apply the charts to the cluster. + HelmDeploy *HelmDeploy `yaml:"helm,omitempty"` + + // KubectlDeploy *beta* uses a client side `kubectl apply` to deploy manifests. + // You'll need a `kubectl` CLI version installed that's compatible with your cluster. + KubectlDeploy *KubectlDeploy `yaml:"kubectl,omitempty"` + + // KustomizeDeploy *beta* uses the `kustomize` CLI to "patch" a deployment for a target environment. + KustomizeDeploy *KustomizeDeploy `yaml:"kustomize,omitempty"` +} + +// KubectlDeploy *beta* uses a client side `kubectl apply` to deploy manifests. +// You'll need a `kubectl` CLI version installed that's compatible with your cluster. +type KubectlDeploy struct { + // Manifests lists the Kubernetes yaml or json manifests. + // Defaults to `["k8s/*.yaml"]`. + Manifests []string `yaml:"manifests,omitempty"` + + // RemoteManifests lists Kubernetes manifests in remote clusters. + RemoteManifests []string `yaml:"remoteManifests,omitempty"` + + // Flags are additional flags passed to `kubectl`. + Flags KubectlFlags `yaml:"flags,omitempty"` +} + +// KubectlFlags are additional flags passed on the command +// line to kubectl either on every command (Global), on creations (Apply) +// or deletions (Delete). +type KubectlFlags struct { + // Global are additional flags passed on every command. + Global []string `yaml:"global,omitempty"` + + // Apply are additional flags passed on creations (`kubectl apply`). + Apply []string `yaml:"apply,omitempty"` + + // Delete are additional flags passed on deletions (`kubectl delete`). + Delete []string `yaml:"delete,omitempty"` + + // DisableValidation passes the `--validate=false` flag to supported + // `kubectl` commands when enabled. + DisableValidation bool `yaml:"disableValidation,omitempty"` +} + +// HelmDeploy *beta* uses the `helm` CLI to apply the charts to the cluster. +type HelmDeploy struct { + // Releases is a list of Helm releases. + Releases []HelmRelease `yaml:"releases,omitempty" yamltags:"required"` + + // Flags are additional option flags that are passed on the command + // line to `helm`. + Flags HelmDeployFlags `yaml:"flags,omitempty"` +} + +// HelmDeployFlags are additional option flags that are passed on the command +// line to `helm`. +type HelmDeployFlags struct { + // Global are additional flags passed on every command. + Global []string `yaml:"global,omitempty"` + + // Install are additional flags passed to (`helm install`). + Install []string `yaml:"install,omitempty"` + + // Upgrade are additional flags passed to (`helm upgrade`). + Upgrade []string `yaml:"upgrade,omitempty"` +} + +// KustomizeDeploy *beta* uses the `kustomize` CLI to "patch" a deployment for a target environment. +type KustomizeDeploy struct { + // KustomizePaths is the path to Kustomization files. + // Defaults to `["."]`. + KustomizePaths []string `yaml:"paths,omitempty"` + + // Flags are additional flags passed to `kubectl`. + Flags KubectlFlags `yaml:"flags,omitempty"` + + // BuildArgs are additional args passed to `kustomize build`. + BuildArgs []string `yaml:"buildArgs,omitempty"` +} + +// HelmRelease describes a helm release to be deployed. +type HelmRelease struct { + // Name is the name of the Helm release. + Name string `yaml:"name,omitempty" yamltags:"required"` + + // ChartPath is the path to the Helm chart. + ChartPath string `yaml:"chartPath,omitempty" yamltags:"required"` + + // ValuesFiles are the paths to the Helm `values` files. + ValuesFiles []string `yaml:"valuesFiles,omitempty"` + + // Values are key-value pairs supplementing the Helm `values` file. + Values map[string]string `yaml:"values,omitempty,omitempty"` + + // Namespace is the Kubernetes namespace. + Namespace string `yaml:"namespace,omitempty"` + + // Version is the version of the chart. + Version string `yaml:"version,omitempty"` + + // SetValues are key-value pairs. + // If present, Skaffold will send `--set` flag to Helm CLI and append all pairs after the flag. + SetValues map[string]string `yaml:"setValues,omitempty"` + + // SetValueTemplates are key-value pairs. + // If present, Skaffold will try to parse the value part of each key-value pair using + // environment variables in the system, then send `--set` flag to Helm CLI and append + // all parsed pairs after the flag. + SetValueTemplates map[string]string `yaml:"setValueTemplates,omitempty"` + + // SetFiles are key-value pairs. + // If present, Skaffold will send `--set-file` flag to Helm CLI and append all pairs after the flag. + SetFiles map[string]string `yaml:"setFiles,omitempty"` + + // Wait if `true`, Skaffold will send `--wait` flag to Helm CLI. + // Defaults to `false`. + Wait bool `yaml:"wait,omitempty"` + + // RecreatePods if `true`, Skaffold will send `--recreate-pods` flag to Helm CLI + // when upgrading a new version of a chart in subsequent dev loop deploy. + // Defaults to `false`. + RecreatePods bool `yaml:"recreatePods,omitempty"` + + // SkipBuildDependencies should build dependencies be skipped. + SkipBuildDependencies bool `yaml:"skipBuildDependencies,omitempty"` + + // UseHelmSecrets instructs skaffold to use secrets plugin on deployment. + UseHelmSecrets bool `yaml:"useHelmSecrets,omitempty"` + + // Remote specifies whether the chart path is remote, or exists on the host filesystem. + // `remote: true` implies `skipBuildDependencies: true`. + Remote bool `yaml:"remote,omitempty"` + + // Overrides are key-value pairs. + // If present, Skaffold will build a Helm `values` file that overrides + // the original and use it to call Helm CLI (`--f` flag). + Overrides util.HelmOverrides `yaml:"overrides,omitempty"` + + // Packaged parameters for packaging helm chart (`helm package`). + Packaged *HelmPackaged `yaml:"packaged,omitempty"` + + // ImageStrategy adds image configurations to the Helm `values` file. + ImageStrategy HelmImageStrategy `yaml:"imageStrategy,omitempty"` +} + +// HelmPackaged parameters for packaging helm chart (`helm package`). +type HelmPackaged struct { + // Version sets the `version` on the chart to this semver version. + Version string `yaml:"version,omitempty"` + + // AppVersion sets the `appVersion` on the chart to this version. + AppVersion string `yaml:"appVersion,omitempty"` +} + +// HelmImageStrategy adds image configurations to the Helm `values` file. +type HelmImageStrategy struct { + HelmImageConfig `yaml:",inline"` +} + +// HelmImageConfig describes an image configuration. +type HelmImageConfig struct { + // HelmFQNConfig is the image configuration uses the syntax `IMAGE-NAME=IMAGE-REPOSITORY:IMAGE-TAG`. + HelmFQNConfig *HelmFQNConfig `yaml:"fqn,omitempty" yamltags:"oneOf=helmImageStrategy"` + + // HelmConventionConfig is the image configuration uses the syntax `IMAGE-NAME.repository=IMAGE-REPOSITORY, IMAGE-NAME.tag=IMAGE-TAG`. + HelmConventionConfig *HelmConventionConfig `yaml:"helm,omitempty" yamltags:"oneOf=helmImageStrategy"` +} + +// HelmFQNConfig is the image config to use the FullyQualifiedImageName as param to set. +type HelmFQNConfig struct { + // Property defines the image config. + Property string `yaml:"property,omitempty"` +} + +// HelmConventionConfig is the image config in the syntax of image.repository and image.tag. +type HelmConventionConfig struct { + // ExplicitRegistry separates `image.registry` to the image config syntax. Useful for some charts e.g. `postgresql`. + ExplicitRegistry bool `yaml:"explicitRegistry,omitempty"` +} + +// Artifact are the items that need to be built, along with the context in which +// they should be built. +type Artifact struct { + // ImageName is the name of the image to be built. + // For example: `gcr.io/k8s-skaffold/example`. + ImageName string `yaml:"image,omitempty" yamltags:"required"` + + // Workspace is the directory containing the artifact's sources. + // Defaults to `.`. + Workspace string `yaml:"context,omitempty"` + + // Sync *beta* lists local files synced to pods instead + // of triggering an image build when modified. + // If no files are listed, sync all the files and infer the destination. + // Defaults to `infer: ["**/*"]`. + Sync *Sync `yaml:"sync,omitempty"` + + // ArtifactType describes how to build an artifact. + ArtifactType `yaml:",inline"` +} + +// Sync *beta* specifies what files to sync into the container. +// This is a list of sync rules indicating the intent to sync for source files. +// If no files are listed, sync all the files and infer the destination. +// Defaults to `infer: ["**/*"]`. +type Sync struct { + // Manual lists manual sync rules indicating the source and destination. + Manual []*SyncRule `yaml:"manual,omitempty" yamltags:"oneOf=sync"` + + // Infer lists file patterns which may be synced into the container. + // The container destination is inferred by the builder. + // Currently only available for docker artifacts. + Infer []string `yaml:"infer,omitempty" yamltags:"oneOf=sync"` + + // Auto delegates discovery of sync rules to the build system. + // Currently only available for jib. + Auto *Auto `yaml:"auto,omitempty" yamltags:"oneOf=sync"` +} + +// SyncRule specifies which local files to sync to remote folders. +type SyncRule struct { + // Src is a glob pattern to match local paths against. + // Directories should be delimited by `/` on all platforms. + // For example: `"css/**/*.css"`. + Src string `yaml:"src,omitempty" yamltags:"required"` + + // Dest is the destination path in the container where the files should be synced to. + // For example: `"app/"` + Dest string `yaml:"dest,omitempty" yamltags:"required"` + + // Strip specifies the path prefix to remove from the source path when + // transplanting the files into the destination folder. + // For example: `"css/"` + Strip string `yaml:"strip,omitempty"` +} + +// Auto cannot be customized. +type Auto struct{} + +// Profile is used to override any `build`, `test` or `deploy` configuration. +type Profile struct { + // Name is a unique profile name. + // For example: `profile-prod`. + Name string `yaml:"name,omitempty" yamltags:"required"` + + // Pipeline contains the definitions to replace the default skaffold pipeline. + Pipeline `yaml:",inline"` + + // Patches lists patches applied to the configuration. + // Patches use the JSON patch notation. + Patches []JSONPatch `yaml:"patches,omitempty"` + + // Activation criteria by which a profile can be auto-activated. + // The profile is auto-activated if any one of the activations are triggered. + // An activation is triggered if all of the criteria (env, kubeContext, command) are triggered. + Activation []Activation `yaml:"activation,omitempty"` +} + +// JSONPatch patch to be applied by a profile. +type JSONPatch struct { + // Op is the operation carried by the patch: `add`, `remove`, `replace`, `move`, `copy` or `test`. + // Defaults to `replace`. + Op string `yaml:"op,omitempty"` + + // Path is the position in the yaml where the operation takes place. + // For example, this targets the `dockerfile` of the first artifact built. + // For example: `/build/artifacts/0/docker/dockerfile`. + Path string `yaml:"path,omitempty" yamltags:"required"` + + // From is the source position in the yaml, used for `copy` or `move` operations. + From string `yaml:"from,omitempty"` + + // Value is the value to apply. Can be any portion of yaml. + Value *util.YamlpatchNode `yaml:"value,omitempty"` +} + +// Activation criteria by which a profile is auto-activated. +type Activation struct { + // Env is a `key=pattern` pair. The profile is auto-activated if an Environment + // Variable `key` matches the pattern. If the pattern starts with `!`, activation + // happens if the remaining pattern is _not_ matched. The pattern matches if the + // Environment Variable value is exactly `pattern`, or the regex `pattern` is + // found in it. An empty `pattern` (e.g. `env: "key="`) always only matches if + // the Environment Variable is undefined or empty. + // For example: `ENV=production` + Env string `yaml:"env,omitempty"` + + // KubeContext is a Kubernetes context for which the profile is auto-activated. + // For example: `minikube`. + KubeContext string `yaml:"kubeContext,omitempty"` + + // Command is a Skaffold command for which the profile is auto-activated. + // For example: `dev`. + Command string `yaml:"command,omitempty"` +} + +// ArtifactType describes how to build an artifact. +type ArtifactType struct { + // DockerArtifact *beta* describes an artifact built from a Dockerfile. + DockerArtifact *DockerArtifact `yaml:"docker,omitempty" yamltags:"oneOf=artifact"` + + // BazelArtifact *beta* requires bazel CLI to be installed and the sources to + // contain [Bazel](https://bazel.build/) configuration files. + BazelArtifact *BazelArtifact `yaml:"bazel,omitempty" yamltags:"oneOf=artifact"` + + // JibArtifact builds images using the + // [Jib plugins for Maven or Gradle](https://github.com/GoogleContainerTools/jib/). + JibArtifact *JibArtifact `yaml:"jib,omitempty" yamltags:"oneOf=artifact"` + + // KanikoArtifact builds images using [kaniko](https://github.com/GoogleContainerTools/kaniko). + KanikoArtifact *KanikoArtifact `yaml:"kaniko,omitempty" yamltags:"oneOf=artifact"` + + // BuildpackArtifact builds images using [Cloud Native Buildpacks](https://buildpacks.io/). + BuildpackArtifact *BuildpackArtifact `yaml:"buildpack,omitempty" yamltags:"oneOf=artifact"` + + // CustomArtifact *beta* builds images using a custom build script written by the user. + CustomArtifact *CustomArtifact `yaml:"custom,omitempty" yamltags:"oneOf=artifact"` +} + +// BuildpackArtifact *alpha* describes an artifact built using [Cloud Native Buildpacks](https://buildpacks.io/). +// It can be used to build images out of project's sources without any additional configuration. +type BuildpackArtifact struct { + // Builder is the builder image used. + Builder string `yaml:"builder" yamltags:"required"` + + // RunImage overrides the stack's default run image. + RunImage string `yaml:"runImage,omitempty"` + + // Env are environment variables, in the `key=value` form, passed to the build. + // Values can use the go template syntax. + // For example: `["key1=value1", "key2=value2", "key3={{.ENV_VARIABLE}}"]`. + Env []string `yaml:"env,omitempty"` + + // Buildpacks is a list of strings, where each string is a specific buildpack to use with the builder. + // If you specify buildpacks the builder image automatic detection will be ignored. These buildpacks will be used to build the Image from your source code. + // Order matters. + Buildpacks []string `yaml:"buildpacks,omitempty"` + + // Dependencies are the file dependencies that skaffold should watch for both rebuilding and file syncing for this artifact. + Dependencies *BuildpackDependencies `yaml:"dependencies,omitempty"` +} + +// BuildpackDependencies *alpha* is used to specify dependencies for an artifact built by a buildpack. +type BuildpackDependencies struct { + // Paths should be set to the file dependencies for this artifact, so that the skaffold file watcher knows when to rebuild and perform file synchronization. + Paths []string `yaml:"paths,omitempty" yamltags:"oneOf=dependency"` + + // Ignore specifies the paths that should be ignored by skaffold's file watcher. If a file exists in both `paths` and in `ignore`, it will be ignored, and will be excluded from both rebuilds and file synchronization. + // Will only work in conjunction with `paths`. + Ignore []string `yaml:"ignore,omitempty"` +} + +// CustomArtifact *beta* describes an artifact built from a custom build script +// written by the user. It can be used to build images with builders that aren't directly integrated with skaffold. +type CustomArtifact struct { + // BuildCommand is the command executed to build the image. + BuildCommand string `yaml:"buildCommand,omitempty"` + // Dependencies are the file dependencies that skaffold should watch for both rebuilding and file syncing for this artifact. + Dependencies *CustomDependencies `yaml:"dependencies,omitempty"` +} + +// CustomDependencies *beta* is used to specify dependencies for an artifact built by a custom build script. +// Either `dockerfile` or `paths` should be specified for file watching to work as expected. +type CustomDependencies struct { + // Dockerfile should be set if the artifact is built from a Dockerfile, from which skaffold can determine dependencies. + Dockerfile *DockerfileDependency `yaml:"dockerfile,omitempty" yamltags:"oneOf=dependency"` + // Command represents a custom command that skaffold executes to obtain dependencies. The output of this command *must* be a valid JSON array. + Command string `yaml:"command,omitempty" yamltags:"oneOf=dependency"` + // Paths should be set to the file dependencies for this artifact, so that the skaffold file watcher knows when to rebuild and perform file synchronization. + Paths []string `yaml:"paths,omitempty" yamltags:"oneOf=dependency"` + // Ignore specifies the paths that should be ignored by skaffold's file watcher. If a file exists in both `paths` and in `ignore`, it will be ignored, and will be excluded from both rebuilds and file synchronization. + // Will only work in conjunction with `paths`. + Ignore []string `yaml:"ignore,omitempty"` +} + +// DockerfileDependency *beta* is used to specify a custom build artifact that is built from a Dockerfile. This allows skaffold to determine dependencies from the Dockerfile. +type DockerfileDependency struct { + // Path locates the Dockerfile relative to workspace. + Path string `yaml:"path,omitempty"` + + // BuildArgs are arguments passed to the docker build. + // It also accepts environment variables via the go template syntax. + // For example: `{"key1": "value1", "key2": "value2", "key3": "'{{.ENV_VARIABLE}}'"}`. + BuildArgs map[string]*string `yaml:"buildArgs,omitempty"` +} + +// KanikoArtifact describes an artifact built from a Dockerfile, +// with kaniko. +type KanikoArtifact struct { + // AdditionalFlags are additional flags to be passed to Kaniko command line. + // See [Kaniko Additional Flags](https://github.com/GoogleContainerTools/kaniko#additional-flags). + // Deprecated - instead the named, unique fields should be used, e.g. `buildArgs`, `cache`, `target`. + AdditionalFlags []string `yaml:"flags,omitempty"` + + // DockerfilePath locates the Dockerfile relative to workspace. + // Defaults to `Dockerfile`. + DockerfilePath string `yaml:"dockerfile,omitempty"` + + // Target is the Dockerfile target name to build. + Target string `yaml:"target,omitempty"` + + // BuildArgs are arguments passed to the docker build. + // It also accepts environment variables via the go template syntax. + // For example: `{"key1": "value1", "key2": "value2", "key3": "'{{.ENV_VARIABLE}}'"}`. + BuildArgs map[string]*string `yaml:"buildArgs,omitempty"` + + // Env are environment variables passed to the kaniko pod. + Env []v1.EnvVar `yaml:"env,omitempty"` + + // InitImage is the image used to run init container which mounts kaniko context. + InitImage string `yaml:"initImage,omitempty"` + + // Image is the Docker image used by the Kaniko pod. + // Defaults to the latest released version of `gcr.io/kaniko-project/executor`. + Image string `yaml:"image,omitempty"` + + // Cache configures Kaniko caching. If a cache is specified, Kaniko will + // use a remote cache which will speed up builds. + Cache *KanikoCache `yaml:"cache,omitempty"` + + // Reproducible is used to strip timestamps out of the built image. + Reproducible bool `yaml:"reproducible,omitempty"` + + // SkipTLS skips TLS verification when pulling and pushing the image. + SkipTLS bool `yaml:"skipTLS,omitempty"` + + // VolumeMounts are volume mounts passed to kaniko pod. + VolumeMounts []v1.VolumeMount `yaml:"volumeMounts,omitempty"` +} + +// DockerArtifact describes an artifact built from a Dockerfile, +// usually using `docker build`. +type DockerArtifact struct { + // DockerfilePath locates the Dockerfile relative to workspace. + // Defaults to `Dockerfile`. + DockerfilePath string `yaml:"dockerfile,omitempty"` + + // Target is the Dockerfile target name to build. + Target string `yaml:"target,omitempty"` + + // BuildArgs are arguments passed to the docker build. + // For example: `{"key1": "value1", "key2": "value2"}`. + BuildArgs map[string]*string `yaml:"buildArgs,omitempty"` + + // NetworkMode is passed through to docker and overrides the + // network configuration of docker builder. If unset, use whatever + // is configured in the underlying docker daemon. Valid modes are + // `host`: use the host's networking stack. + // `bridge`: use the bridged network configuration. + // `none`: no networking in the container. + NetworkMode string `yaml:"network,omitempty"` + + // CacheFrom lists the Docker images used as cache sources. + // For example: `["golang:1.10.1-alpine3.7", "alpine:3.7"]`. + CacheFrom []string `yaml:"cacheFrom,omitempty"` + + // NoCache used to pass in --no-cache to docker build to prevent caching. + NoCache bool `yaml:"noCache,omitempty"` +} + +// BazelArtifact describes an artifact built with [Bazel](https://bazel.build/). +type BazelArtifact struct { + // BuildTarget is the `bazel build` target to run. + // For example: `//:skaffold_example.tar`. + BuildTarget string `yaml:"target,omitempty" yamltags:"required"` + + // BuildArgs are additional args to pass to `bazel build`. + // For example: `["-flag", "--otherflag"]`. + BuildArgs []string `yaml:"args,omitempty"` +} + +// JibArtifact builds images using the +// [Jib plugins for Maven and Gradle](https://github.com/GoogleContainerTools/jib/). +type JibArtifact struct { + // Project selects which sub-project to build for multi-module builds. + Project string `yaml:"project,omitempty"` + + // Flags are additional build flags passed to the builder. + // For example: `["--no-build-cache"]`. + Flags []string `yaml:"args,omitempty"` + + // Type the Jib builder type; normally determined automatically. Valid types are + // `maven`: for Maven. + // `gradle`: for Gradle. + Type string `yaml:"type,omitempty"` +} diff --git a/pkg/skaffold/schema/v2beta1/upgrade.go b/pkg/skaffold/schema/v2beta1/upgrade.go new file mode 100755 index 00000000000..cb63d79679e --- /dev/null +++ b/pkg/skaffold/schema/v2beta1/upgrade.go @@ -0,0 +1,28 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2beta1 + +import ( + "errors" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" +) + +// Upgrade upgrades a configuration to the next version. +func (c *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { + return nil, errors.New("not implemented yet") +}