-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into add_skaffold_dev_loop_events
- Loading branch information
Showing
10 changed files
with
407 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
Copyright 2020 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 debug | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
cnb "github.com/buildpacks/lifecycle" | ||
shell "github.com/kballard/go-shellquote" | ||
"github.com/sirupsen/logrus" | ||
v1 "k8s.io/api/core/v1" | ||
) | ||
|
||
func init() { | ||
// the CNB's launcher just launches the command | ||
entrypointLaunchers = append(entrypointLaunchers, "/cnb/lifecycle/launcher") | ||
} | ||
|
||
// updateForCNBImage normalizes an imageConfiguration from a Cloud Native Buildpacks-created image | ||
// prior to handing off to another transformer. This transformer is usually the `debug` container | ||
// transform process. CNB images have their entrypoint set to the CNB launcher, and the image command | ||
// describe the launch parameters. After the transformer, updateForCNBImage rewrites the changed | ||
// image command back to the form expected by the CNB launcher. | ||
// | ||
// The CNB launcher supports three types of launches: | ||
// | ||
// 1. _predefined processes_ are named sets of command+arguments. There are two types: | ||
// - direct: these are passed uninterpreted to os.exec; the command is resolved in PATH | ||
// - script: the command is treated as a shell script and passed to `sh -c`, the remaining | ||
// arguments are added to the shell, and so available as positional arguments. | ||
// For example: `sh -c 'echo $0 $1 $2 $3' arg0 arg1 arg2 arg3` => `arg0 arg1 arg2 arg3`. | ||
// (https://github.com/buildpacks/lifecycle/issues/218#issuecomment-567091462). | ||
// 2. _direct execs_ where the container's arg[0] == `--` are treated like direct processes | ||
// 3. _shell scripts_ where the container's arg[0] is the script and treated are like indirect processes. | ||
// | ||
// A key point is that the script-style launches allow support references to environment variables. | ||
// So we find the command line to be executed, whether that is a script or a direct, and turn it into | ||
// a normal command-line. But we also return a rewriter to transform the the command-line back to | ||
// the original form. | ||
func updateForCNBImage(container *v1.Container, ic imageConfiguration, transformer func(container *v1.Container, ic imageConfiguration) (ContainerDebugConfiguration, string, error)) (ContainerDebugConfiguration, string, error) { | ||
// The build metadata isn't absolutely required as the image args could be | ||
// a command line (e.g., `python xxx`) but it likely indicates the | ||
// image was built with an older lifecycle. | ||
// buildpacks/lifecycle 0.6.0 now embeds processes into special image label | ||
metadataJSON, found := ic.labels["io.buildpacks.build.metadata"] | ||
if !found { | ||
return ContainerDebugConfiguration{}, "", fmt.Errorf("image is missing buildpacks metadata; perhaps built with older lifecycle?") | ||
} | ||
m := cnb.BuildMetadata{} | ||
if err := json.Unmarshal([]byte(metadataJSON), &m); err != nil { | ||
return ContainerDebugConfiguration{}, "", fmt.Errorf("unable to parse image buildpacks metadata") | ||
} | ||
if len(m.Processes) == 0 { | ||
return ContainerDebugConfiguration{}, "", fmt.Errorf("buildpacks metadata has no processes") | ||
} | ||
|
||
// the buildpacks launcher is retained as the entrypoint | ||
ic, rewriter := adjustCommandLine(m, ic) | ||
c, img, err := transformer(container, ic) | ||
if err == nil && rewriter != nil { | ||
container.Args = rewriter(container.Args) | ||
} | ||
return c, img, err | ||
} | ||
|
||
// adjustCommandLine resolves the launch process and then rewrites the command-line to be | ||
// in a form suitable for the normal `skaffold debug` transformations. It returns an | ||
// amended configuration with a function to re-transform the command-line to the form | ||
// expected by the launcher. | ||
func adjustCommandLine(m cnb.BuildMetadata, ic imageConfiguration) (imageConfiguration, func([]string) []string) { | ||
// direct exec | ||
if len(ic.arguments) > 0 && ic.arguments[0] == "--" { | ||
// strip and restore the "--" | ||
ic.arguments = ic.arguments[1:] | ||
return ic, func(transformed []string) []string { | ||
return append([]string{"--"}, transformed...) | ||
} | ||
} | ||
|
||
processType := "web" // default buildpacks process type | ||
// the launcher accepts the first argument as a process type | ||
if len(ic.arguments) == 1 { | ||
processType = ic.arguments[0] | ||
} else if value := ic.env["CNB_PROCESS_TYPE"]; len(value) > 0 { | ||
processType = value | ||
} | ||
|
||
for _, p := range m.Processes { | ||
if p.Type == processType { | ||
if p.Direct { | ||
// p.Command is the command and p.Args are the arguments | ||
ic.arguments = append([]string{p.Command}, p.Args...) | ||
return ic, func(transformed []string) []string { | ||
return append([]string{"--"}, transformed...) | ||
} | ||
} | ||
// Script type: split p.Command, pass it through the transformer, and then reassemble in the rewriter. | ||
if args, err := shell.Split(p.Command); err == nil { | ||
ic.arguments = args | ||
} else { | ||
ic.arguments = []string{p.Command} | ||
} | ||
return ic, func(transformed []string) []string { | ||
// reassemble back into a script with arguments | ||
return append([]string{shJoin(transformed)}, p.Args...) | ||
} | ||
} | ||
} | ||
|
||
if len(ic.arguments) == 0 { | ||
// indicates an image mis-configuration as we should have resolved the the | ||
// CNB_PROCESS_TYPE (if specified) or `web`. | ||
logrus.Warnf("no CNB launch found for %s/%s", ic.artifact, processType) | ||
return ic, nil | ||
} | ||
|
||
// ic.arguments[0] is a shell script: split it, pass it through the transformer, and then reassemble in the rewriter. | ||
// If it can't be split, then we fall through and return it untouched, to be handled by the normal debug process. | ||
var rewriter func(transformed []string) []string | ||
if args, err := shell.Split(ic.arguments[0]); err == nil { | ||
remnants := ic.arguments[1:] | ||
ic.arguments = args | ||
rewriter = func(transformed []string) []string { | ||
// reassemble back into a script with arguments | ||
return append([]string{shJoin(transformed)}, remnants...) | ||
} | ||
} | ||
return ic, rewriter | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
Copyright 2020 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 debug | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
cnb "github.com/buildpacks/lifecycle" | ||
"github.com/buildpacks/lifecycle/launch" | ||
v1 "k8s.io/api/core/v1" | ||
|
||
"github.com/GoogleContainerTools/skaffold/testutil" | ||
) | ||
|
||
func TestUpdateForCNBImage(t *testing.T) { | ||
// metadata with default process type | ||
md := cnb.BuildMetadata{Processes: []launch.Process{ | ||
{Type: "web", Command: "webProcess", Args: []string{"webArg1", "webArg2"}}, | ||
{Type: "diag", Command: "diagProcess"}, | ||
{Type: "direct", Command: "command", Args: []string{"cmdArg1"}, Direct: true}, | ||
}} | ||
mdMarshalled, _ := json.Marshal(&md) | ||
mdJSON := string(mdMarshalled) | ||
// metadata with no default process type | ||
mdnd := cnb.BuildMetadata{Processes: []launch.Process{ | ||
{Type: "diag", Command: "diagProcess"}, | ||
{Type: "direct", Command: "command", Args: []string{"cmdArg1"}, Direct: true}, | ||
}} | ||
mdndMarshalled, _ := json.Marshal(&mdnd) | ||
mdndJSON := string(mdndMarshalled) | ||
|
||
tests := []struct { | ||
description string | ||
input imageConfiguration | ||
shouldErr bool | ||
expected v1.Container | ||
}{ | ||
{ | ||
description: "error when missing build.metadata", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}}, | ||
shouldErr: true, | ||
}, | ||
{ | ||
description: "error when build.metadata missing processes", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, labels: map[string]string{"io.buildpacks.build.metadata": "{}"}}, | ||
shouldErr: true, | ||
}, | ||
{ | ||
description: "direct command-lines are rewritten as direct command-lines", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, arguments: []string{"--", "web", "arg1", "arg2"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"--", "web", "arg1", "arg2"}}, | ||
}, | ||
{ | ||
description: "defaults to web process when no process type", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"webProcess", "webArg1", "webArg2"}}, | ||
}, | ||
{ | ||
description: "resolves to default 'web' process", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"webProcess", "webArg1", "webArg2"}}, | ||
}, | ||
{ | ||
description: "CNB_PROCESS_TYPE=web", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, env: map[string]string{"CNB_PROCESS_TYPE": "web"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"webProcess", "webArg1", "webArg2"}}, | ||
}, | ||
{ | ||
description: "CNB_PROCESS_TYPE=diag", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, env: map[string]string{"CNB_PROCESS_TYPE": "diag"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"diagProcess"}}, | ||
}, | ||
{ | ||
description: "CNB_PROCESS_TYPE=direct", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, env: map[string]string{"CNB_PROCESS_TYPE": "direct"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"--", "command", "cmdArg1"}}, | ||
}, | ||
{ | ||
description: "script command-line", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, arguments: []string{"python main.py"}, labels: map[string]string{"io.buildpacks.build.metadata": mdJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{Args: []string{"python main.py"}}, | ||
}, | ||
{ | ||
description: "no process and no args", | ||
input: imageConfiguration{entrypoint: []string{"/cnb/lifecycle/launcher"}, labels: map[string]string{"io.buildpacks.build.metadata": mdndJSON}}, | ||
shouldErr: false, | ||
expected: v1.Container{}, | ||
}, | ||
} | ||
for _, test := range tests { | ||
testutil.Run(t, test.description, func(t *testutil.T) { | ||
dummyTransform := func(c *v1.Container, ic imageConfiguration) (ContainerDebugConfiguration, string, error) { | ||
c.Args = ic.arguments | ||
return ContainerDebugConfiguration{}, "", nil | ||
} | ||
|
||
copy := v1.Container{} | ||
_, _, err := updateForCNBImage(©, test.input, dummyTransform) | ||
t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, copy) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.