Skip to content

Commit c75c551

Browse files
authored
Abstract k8s container representation from debug transformers (#6335)
* Abstract k8s container representation from debug transformers * Use ContainerAdapter to abstract away kubernetes packages from debug package * Move all k8s-related code out of pkg/skaffold/debug * consolidate annotations and types packages * preserve ValueFrom from k8s env * make containerTransformers a map, simplify ValueFrom in adapter
1 parent 0925a54 commit c75c551

34 files changed

+4092
-3638
lines changed

cmd/skaffold/app/cmd/debug.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import (
2222

2323
"github.com/spf13/cobra"
2424

25-
debugging "github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug"
25+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug"
26+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/debugging"
2627
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
2728
)
2829

@@ -38,7 +39,7 @@ func NewCmdDebug() *cobra.Command {
3839
"Auto-build and sync is disabled by default to prevent accidentally tearing down debug sessions.").
3940
WithCommonFlags().
4041
WithFlags([]*Flag{
41-
{Value: &debugging.Protocols, Name: "protocols", DefValue: []string{}, Usage: "Priority sorted order of debugger protocols to support."},
42+
{Value: &debug.Protocols, Name: "protocols", DefValue: []string{}, Usage: "Priority sorted order of debugger protocols to support."},
4243
}).
4344
WithExample("Launch with port-forwarding", "debug --port-forward").
4445
WithHouseKeepingMessages().

cmd/skaffold/app/cmd/filter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import (
2626

2727
"github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/flags"
2828
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
29-
debugging "github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug"
3029
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
30+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/debugging"
3131
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
3232
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
3333
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"

integration/debug_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/GoogleContainerTools/skaffold/integration/skaffold"
26-
debugannotations "github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/annotations"
26+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/types"
2727
"github.com/GoogleContainerTools/skaffold/proto/v1"
2828
"github.com/GoogleContainerTools/skaffold/testutil"
2929
)
@@ -77,7 +77,7 @@ func TestDebug(t *testing.T) {
7777
skaffold.Debug(test.args...).InDir(test.dir).InNs(ns.Name).RunBackground(t)
7878

7979
verifyDebugAnnotations := func(annotations map[string]string) {
80-
var configs map[string]debugannotations.ContainerDebugConfiguration
80+
var configs map[string]types.ContainerDebugConfiguration
8181
if anno, found := annotations["debug.cloud.google.com/config"]; !found {
8282
t.Errorf("deployment missing debug annotation: %v", annotations)
8383
} else if err := json.Unmarshal([]byte(anno), &configs); err != nil {

pkg/skaffold/debug/apply_transforms.go

+14-64
Original file line numberDiff line numberDiff line change
@@ -17,71 +17,21 @@ limitations under the License.
1717
package debug
1818

1919
import (
20-
"bufio"
21-
"bytes"
2220
"context"
2321
"fmt"
2422
"strings"
2523

26-
"github.com/sirupsen/logrus"
27-
"k8s.io/apimachinery/pkg/runtime"
28-
"k8s.io/apimachinery/pkg/runtime/serializer/json"
29-
"k8s.io/client-go/kubernetes/scheme"
30-
3124
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
3225
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
33-
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
3426
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
3527
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
3628
)
3729

38-
var (
39-
decodeFromYaml = scheme.Codecs.UniversalDeserializer().Decode
40-
encodeAsYaml = func(o runtime.Object) ([]byte, error) {
41-
s := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme)
42-
var b bytes.Buffer
43-
w := bufio.NewWriter(&b)
44-
if err := s.Encode(o, w); err != nil {
45-
return nil, err
46-
}
47-
w.Flush()
48-
return b.Bytes(), nil
49-
}
50-
)
51-
52-
// ApplyDebuggingTransforms applies language-platform-specific transforms to a list of manifests.
53-
func ApplyDebuggingTransforms(l manifest.ManifestList, builds []graph.Artifact, registries manifest.Registries) (manifest.ManifestList, error) {
54-
ctx, cancel := context.WithCancel(context.Background())
55-
defer cancel()
56-
57-
retriever := func(image string) (imageConfiguration, error) {
58-
if artifact := findArtifact(image, builds); artifact != nil {
59-
return retrieveImageConfiguration(ctx, artifact, registries.InsecureRegistries)
60-
}
61-
return imageConfiguration{}, fmt.Errorf("no build artifact for %q", image)
62-
}
63-
return applyDebuggingTransforms(l, retriever, registries.DebugHelpersRegistry)
64-
}
65-
66-
func applyDebuggingTransforms(l manifest.ManifestList, retriever configurationRetriever, debugHelpersRegistry string) (manifest.ManifestList, error) {
67-
var updated manifest.ManifestList
68-
for _, manifest := range l {
69-
obj, _, err := decodeFromYaml(manifest, nil, nil)
70-
if err != nil {
71-
log.Entry(context.TODO()).Debugf("Unable to interpret manifest for debugging: %v\n", err)
72-
} else if transformManifest(obj, retriever, debugHelpersRegistry) {
73-
manifest, err = encodeAsYaml(obj)
74-
if err != nil {
75-
return nil, fmt.Errorf("marshalling yaml: %w", err)
76-
}
77-
if logrus.IsLevelEnabled(logrus.DebugLevel) {
78-
log.Entry(context.TODO()).Debugln("Applied debugging transform:\n", string(manifest))
79-
}
80-
}
81-
updated = append(updated, manifest)
30+
var ConfigRetriever = func(ctx context.Context, image string, builds []graph.Artifact, registries map[string]bool) (ImageConfiguration, error) {
31+
if artifact := findArtifact(image, builds); artifact != nil {
32+
return retrieveImageConfiguration(ctx, artifact, registries)
8233
}
83-
84-
return updated, nil
34+
return ImageConfiguration{}, fmt.Errorf("no build artifact for %q", image)
8535
}
8636

8737
// findArtifact finds the corresponding artifact for the given image.
@@ -102,32 +52,32 @@ func findArtifact(image string, builds []graph.Artifact) *graph.Artifact {
10252

10353
// retrieveImageConfiguration retrieves the image container configuration for
10454
// the given build artifact
105-
func retrieveImageConfiguration(ctx context.Context, artifact *graph.Artifact, insecureRegistries map[string]bool) (imageConfiguration, error) {
55+
func retrieveImageConfiguration(ctx context.Context, artifact *graph.Artifact, insecureRegistries map[string]bool) (ImageConfiguration, error) {
10656
// TODO: use the proper RunContext
10757
apiClient, err := docker.NewAPIClient(ctx, &runcontext.RunContext{
10858
InsecureRegistries: insecureRegistries,
10959
})
11060
if err != nil {
111-
return imageConfiguration{}, fmt.Errorf("could not connect to local docker daemon: %w", err)
61+
return ImageConfiguration{}, fmt.Errorf("could not connect to local docker daemon: %w", err)
11262
}
11363

11464
// the apiClient will go to the remote registry if local docker daemon is not available
11565
manifest, err := apiClient.ConfigFile(ctx, artifact.Tag)
11666
if err != nil {
11767
log.Entry(ctx).Debugf("Error retrieving image manifest for %v: %v", artifact.Tag, err)
118-
return imageConfiguration{}, fmt.Errorf("retrieving image config for %q: %w", artifact.Tag, err)
68+
return ImageConfiguration{}, fmt.Errorf("retrieving image config for %q: %w", artifact.Tag, err)
11969
}
12070

12171
config := manifest.Config
12272
log.Entry(ctx).Debugf("Retrieved local image configuration for %v: %v", artifact.Tag, config)
12373
// need to duplicate slices as apiClient caches requests
124-
return imageConfiguration{
125-
artifact: artifact.ImageName,
126-
env: envAsMap(config.Env),
127-
entrypoint: dupArray(config.Entrypoint),
128-
arguments: dupArray(config.Cmd),
129-
labels: dupMap(config.Labels),
130-
workingDir: config.WorkingDir,
74+
return ImageConfiguration{
75+
Artifact: artifact.ImageName,
76+
Env: envAsMap(config.Env),
77+
Entrypoint: dupArray(config.Entrypoint),
78+
Arguments: dupArray(config.Cmd),
79+
Labels: dupMap(config.Labels),
80+
WorkingDir: config.WorkingDir,
13181
}, nil
13282
}
13383

pkg/skaffold/debug/cnb.go

+41-41
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ import (
2525
cnb "github.com/buildpacks/lifecycle"
2626
cnbl "github.com/buildpacks/lifecycle/launch"
2727
shell "github.com/kballard/go-shellquote"
28-
v1 "k8s.io/api/core/v1"
2928

30-
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/annotations"
29+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/types"
3130
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
3231
)
3332

@@ -51,16 +50,16 @@ func init() {
5150
// CNB images use a special launcher as the entrypoint. In CNB Platform API 0.3,
5251
// this was always `/cnb/lifecycle/launcher`, but Platform API 0.4 (introduced in pack 0.13)
5352
// allows using a symlink to a file in `/cnb/process/<type>`. More below.
54-
func isCNBImage(ic imageConfiguration) bool {
55-
if _, found := ic.labels["io.buildpacks.stack.id"]; !found {
53+
func isCNBImage(ic ImageConfiguration) bool {
54+
if _, found := ic.Labels["io.buildpacks.stack.id"]; !found {
5655
return false
5756
}
58-
return len(ic.entrypoint) == 1 && (ic.entrypoint[0] == cnbLauncher || strings.HasPrefix(ic.entrypoint[0], cnbProcessLauncherPrefix))
57+
return len(ic.Entrypoint) == 1 && (ic.Entrypoint[0] == cnbLauncher || strings.HasPrefix(ic.Entrypoint[0], cnbProcessLauncherPrefix))
5958
}
6059

6160
// hasCNBLauncherEntrypoint returns true if the entrypoint is the cnbLauncher.
62-
func hasCNBLauncherEntrypoint(ic imageConfiguration) bool {
63-
return len(ic.entrypoint) == 1 && ic.entrypoint[0] == cnbLauncher
61+
func hasCNBLauncherEntrypoint(ic ImageConfiguration) bool {
62+
return len(ic.Entrypoint) == 1 && ic.Entrypoint[0] == cnbLauncher
6463
}
6564

6665
// updateForCNBImage normalizes a CNB image by rewriting the CNB launch configuration into
@@ -106,43 +105,44 @@ func hasCNBLauncherEntrypoint(ic imageConfiguration) bool {
106105
// the default process type. `CNB_PROCESS_TYPE` is ignored in this situation. A different process
107106
// can be used by overriding the image entrypoint. Direct and script launches are supported by
108107
// setting the entrypoint to `/cnb/lifecycle/launcher` and providing the appropriate arguments.
109-
func updateForCNBImage(container *v1.Container, ic imageConfiguration, transformer func(container *v1.Container, ic imageConfiguration) (annotations.ContainerDebugConfiguration, string, error)) (annotations.ContainerDebugConfiguration, string, error) {
108+
func updateForCNBImage(adapter types.ContainerAdapter, ic ImageConfiguration, transformer func(adapter types.ContainerAdapter, ic ImageConfiguration) (types.ContainerDebugConfiguration, string, error)) (types.ContainerDebugConfiguration, string, error) {
110109
// buildpacks/lifecycle 0.6.0 embeds the process definitions into a special image label.
111110
// The build metadata isn't absolutely required as the image args could be
112111
// a command line (e.g., `python xxx`) but it likely indicates the
113112
// image was built with an older lifecycle.
114-
metadataJSON, found := ic.labels["io.buildpacks.build.metadata"]
113+
metadataJSON, found := ic.Labels["io.buildpacks.build.metadata"]
115114
if !found {
116-
return annotations.ContainerDebugConfiguration{}, "", fmt.Errorf("image is missing buildpacks metadata; perhaps built with older lifecycle?")
115+
return types.ContainerDebugConfiguration{}, "", fmt.Errorf("image is missing buildpacks metadata; perhaps built with older lifecycle?")
117116
}
118117
m := cnb.BuildMetadata{}
119118
if err := json.Unmarshal([]byte(metadataJSON), &m); err != nil {
120-
return annotations.ContainerDebugConfiguration{}, "", fmt.Errorf("unable to parse image buildpacks metadata")
119+
return types.ContainerDebugConfiguration{}, "", fmt.Errorf("unable to parse image buildpacks metadata")
121120
}
122121
if len(m.Processes) == 0 {
123-
return annotations.ContainerDebugConfiguration{}, "", fmt.Errorf("buildpacks metadata has no processes")
122+
return types.ContainerDebugConfiguration{}, "", fmt.Errorf("buildpacks metadata has no processes")
124123
}
125124

126-
needsCnbLauncher := ic.entrypoint[0] != cnbLauncher
125+
needsCnbLauncher := ic.Entrypoint[0] != cnbLauncher
127126
// Rewrites the command-line with cnbLauncher as the entrypoint
128127
ic, rewriter := adjustCommandLine(m, ic)
129128

130129
// The CNB launcher uses CNB_APP_DIR (defaults to /workspace) and ignores the image's working directory.
131-
if appDir := ic.env["CNB_APP_DIR"]; appDir != "" {
132-
ic.workingDir = appDir
130+
if appDir := ic.Env["CNB_APP_DIR"]; appDir != "" {
131+
ic.WorkingDir = appDir
133132
} else {
134-
ic.workingDir = "/workspace"
133+
ic.WorkingDir = "/workspace"
135134
}
136135

137-
c, img, err := transformer(container, ic)
136+
c, img, err := transformer(adapter, ic)
138137
if err != nil {
139138
return c, img, err
140139
}
141140
// must explicitly modify the working dir as the imageConfig is lost after we return
142141
if c.WorkingDir == "" {
143-
c.WorkingDir = ic.workingDir
142+
c.WorkingDir = ic.WorkingDir
144143
}
145144

145+
container := adapter.GetContainer()
146146
if container.Args != nil && rewriter != nil {
147147
// Only rewrite the container if the arguments were changed: some transforms only alter
148148
// env vars, and the image's arguments are not changed.
@@ -158,11 +158,11 @@ func updateForCNBImage(container *v1.Container, ic imageConfiguration, transform
158158
// in a form suitable for the normal `skaffold debug` transformations. It returns an
159159
// amended configuration with a function to re-transform the command-line to the form
160160
// expected by cnbLauncher.
161-
func adjustCommandLine(m cnb.BuildMetadata, ic imageConfiguration) (imageConfiguration, func([]string) []string) {
161+
func adjustCommandLine(m cnb.BuildMetadata, ic ImageConfiguration) (ImageConfiguration, func([]string) []string) {
162162
// check for direct exec
163-
if hasCNBLauncherEntrypoint(ic) && len(ic.arguments) > 0 && ic.arguments[0] == "--" {
163+
if hasCNBLauncherEntrypoint(ic) && len(ic.Arguments) > 0 && ic.Arguments[0] == "--" {
164164
// strip and then restore the "--"
165-
ic.arguments = ic.arguments[1:]
165+
ic.Arguments = ic.Arguments[1:]
166166
return ic, func(transformed []string) []string {
167167
return append([]string{"--"}, transformed...)
168168
}
@@ -180,19 +180,19 @@ func adjustCommandLine(m cnb.BuildMetadata, ic imageConfiguration) (imageConfigu
180180
} else {
181181
args := append([]string{p.Command}, p.Args...)
182182
args = append(args, clArgs...)
183-
ic.entrypoint = []string{cnbLauncher}
184-
ic.arguments = args
183+
ic.Entrypoint = []string{cnbLauncher}
184+
ic.Arguments = args
185185
return ic, func(transformed []string) []string {
186186
return append([]string{"--"}, transformed...)
187187
}
188188
}
189189
}
190190
// Script type: split p.Command, pass it through the transformer, and then reassemble in the rewriter.
191-
ic.entrypoint = []string{cnbLauncher}
191+
ic.Entrypoint = []string{cnbLauncher}
192192
if args, err := shell.Split(p.Command); err == nil {
193-
ic.arguments = args
193+
ic.Arguments = args
194194
} else {
195-
ic.arguments = []string{p.Command}
195+
ic.Arguments = []string{p.Command}
196196
}
197197
return ic, func(transformed []string) []string {
198198
// reassemble back into a script with arguments
@@ -202,16 +202,16 @@ func adjustCommandLine(m cnb.BuildMetadata, ic imageConfiguration) (imageConfigu
202202
}
203203
}
204204

205-
if len(ic.arguments) == 0 {
206-
log.Entry(context.TODO()).Warnf("no CNB launch found for %s", ic.artifact)
205+
if len(ic.Arguments) == 0 {
206+
log.Entry(context.TODO()).Warnf("no CNB launch found for %s", ic.Artifact)
207207
return ic, nil
208208
}
209209

210210
// ic.arguments[0] is a shell script: split it, pass it through the transformer, and then reassemble in the rewriter.
211211
// If it can't be split, then we return it untouched, to be handled by the normal debug process.
212-
if cmdline, err := shell.Split(ic.arguments[0]); err == nil {
213-
positionals := ic.arguments[1:] // save aside the script positional arguments
214-
ic.arguments = cmdline
212+
if cmdline, err := shell.Split(ic.Arguments[0]); err == nil {
213+
positionals := ic.Arguments[1:] // save aside the script positional arguments
214+
ic.Arguments = cmdline
215215
return ic, func(transformed []string) []string {
216216
// reassemble back into a script with the positional arguments
217217
return append([]string{shJoin(transformed)}, positionals...)
@@ -222,11 +222,11 @@ func adjustCommandLine(m cnb.BuildMetadata, ic imageConfiguration) (imageConfigu
222222

223223
// findCNBProcess tries to resolve a CNB process definition given the image configuration.
224224
// It is assumed that the image is a CNB image.
225-
func findCNBProcess(ic imageConfiguration, m cnb.BuildMetadata) (cnbl.Process, []string, bool) {
226-
if hasCNBLauncherEntrypoint(ic) && len(ic.arguments) > 0 {
225+
func findCNBProcess(ic ImageConfiguration, m cnb.BuildMetadata) (cnbl.Process, []string, bool) {
226+
if hasCNBLauncherEntrypoint(ic) && len(ic.Arguments) > 0 {
227227
// the launcher accepts the first argument as a process type
228-
if len(ic.arguments) == 1 {
229-
processType := ic.arguments[0]
228+
if len(ic.Arguments) == 1 {
229+
processType := ic.Arguments[0]
230230
for _, p := range m.Processes {
231231
if p.Type == processType {
232232
return p, nil, true // drop the argument
@@ -238,20 +238,20 @@ func findCNBProcess(ic imageConfiguration, m cnb.BuildMetadata) (cnbl.Process, [
238238

239239
// determine process-type
240240
processType := "web" // default buildpacks process type
241-
platformAPI := ic.env["CNB_PLATFORM_API"]
241+
platformAPI := ic.Env["CNB_PLATFORM_API"]
242242
if platformAPI == "0.4" {
243243
// Platform API 0.4-style /cnb/process/xxx
244-
if !strings.HasPrefix(ic.entrypoint[0], cnbProcessLauncherPrefix) {
244+
if !strings.HasPrefix(ic.Entrypoint[0], cnbProcessLauncherPrefix) {
245245
return cnbl.Process{}, nil, false
246246
}
247-
processType = ic.entrypoint[0][len(cnbProcessLauncherPrefix):]
248-
} else if len(ic.env["CNB_PROCESS_TYPE"]) > 0 {
249-
processType = ic.env["CNB_PROCESS_TYPE"]
247+
processType = ic.Entrypoint[0][len(cnbProcessLauncherPrefix):]
248+
} else if len(ic.Env["CNB_PROCESS_TYPE"]) > 0 {
249+
processType = ic.Env["CNB_PROCESS_TYPE"]
250250
}
251251

252252
for _, p := range m.Processes {
253253
if p.Type == processType {
254-
return p, ic.arguments, true
254+
return p, ic.Arguments, true
255255
}
256256
}
257257
return cnbl.Process{}, nil, false

0 commit comments

Comments
 (0)