diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6aa41727..77047eb3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,13 +2,18 @@ ## Testing Guidelines +- **TDD Approach**: Follow Red-Green-Refactor cycle for all new features + - **Red**: Write failing tests first that describe the desired behavior + - **Green**: Write minimal code to make tests pass + - **Refactor**: Clean up code while keeping all tests passing - **No temporary test files**: Never create standalone test files like "test-*.dockerfile", "quick-test.*", etc. - **Use existing test suites**: Add proper test cases to existing test files in the appropriate `*_test.go` files - **Follow test patterns**: Use the established testing patterns in the codebase (table-driven tests, proper assertions) - **Clean testing**: If testing is needed, extend the existing test suite rather than creating throwaway files - **Focus on correct behavior**: Tests should verify expected functionality, not document bugs or regressions -- **Positive test naming**: Name tests to describe what they verify (e.g., `Test_separateScratchConnections`) rather than what's broken +- **Test naming**: Name tests to describe what they verify (e.g., `Test_separateScratchConnections`) rather than what's broken - **Clear test comments**: Comments should explain what behavior is being tested, not reference historical issues +- **Comprehensive coverage**: Ensure edge cases, error conditions, and integration scenarios are tested ## Code Quality @@ -16,3 +21,4 @@ - Use proper error handling - Write comprehensive tests for new functionality - Maintain backward compatibility unless explicitly breaking changes are needed +- Always use `make check` for efficient linting and testing - it runs golangci-lint, tests with coverage, and enforces code quality standards in one command diff --git a/README.md b/README.md index 373dd56e..95793ebd 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Flags: -n, --nodesep float minimum space between two adjacent nodes in the same rank (default 1) -o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf) -r, --ranksep float minimum separation between ranks (default 0.5) - --separate-scratch create separate nodes for each scratch image instead of collapsing them + --scratch how to handle scratch images, one of: collapsed, hidden, separated (default collapsed) -u, --unflatten uint stagger length of leaf edges between [1,u] (default 0) --version display the version of dockerfilegraph ``` diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 5b706379..0ed4de9b 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -15,19 +15,19 @@ import ( ) var ( - concentrateFlag bool - dpiFlag uint - edgestyleFlag enum - filenameFlag string - layersFlag bool - legendFlag bool - maxLabelLengthFlag uint - nodesepFlag float64 - outputFlag enum - ranksepFlag float64 - separateScratchFlag bool - unflattenFlag uint - versionFlag bool + concentrateFlag bool + dpiFlag uint + edgestyleFlag enum + filenameFlag string + layersFlag bool + legendFlag bool + maxLabelLengthFlag uint + nodesepFlag float64 + outputFlag enum + ranksepFlag float64 + scratchFlag enum + unflattenFlag uint + versionFlag bool ) // dfgWriter is a writer that prints to stdout. When testing, we @@ -63,12 +63,15 @@ It creates a visual graph representation of the build process.`, return } + // Determine scratch mode from flag + scratchMode := scratchFlag.String() + // Load and parse the Dockerfile. dockerfile, err := dockerfile2dot.LoadAndParseDockerfile( inputFS, filenameFlag, int(maxLabelLengthFlag), - separateScratchFlag, + scratchMode, ) if err != nil { return @@ -229,11 +232,11 @@ It creates a visual graph representation of the build process.`, "minimum separation between ranks", ) - rootCmd.Flags().BoolVar( - &separateScratchFlag, - "separate-scratch", - false, - "create separate nodes for each scratch image instead of collapsing them", + scratchFlag = newEnum("collapsed", "separated", "hidden") + rootCmd.Flags().Var( + &scratchFlag, + "scratch", + "how to handle scratch images, one of: "+strings.Join(scratchFlag.AllowedValues(), ", "), ) rootCmd.Flags().UintVarP( diff --git a/internal/cmd/root_test.go b/internal/cmd/root_test.go index b14b581c..ff36ef89 100644 --- a/internal/cmd/root_test.go +++ b/internal/cmd/root_test.go @@ -38,7 +38,7 @@ Flags: -n, --nodesep float minimum space between two adjacent nodes in the same rank (default 1) -o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf) -r, --ranksep float minimum separation between ranks (default 0.5) - --separate-scratch create separate nodes for each scratch image instead of collapsing them + --scratch how to handle scratch images, one of: collapsed, hidden, separated (default collapsed) -u, --unflatten uint stagger length of leaf edges between [1,u] (default 0) --version display the version of dockerfilegraph ` @@ -492,8 +492,29 @@ It creates a visual graph representation of the build process. `, }, { - name: "separate scratch flag", - cliArgs: []string{"--separate-scratch", "-o", "raw"}, + name: "scratch flag collapsed mode", + cliArgs: []string{"--scratch", "collapsed", "-o", "raw"}, + dockerfileContent: "FROM scratch AS app1\nCOPY app1.txt /app1.txt\n\n" + + "FROM scratch AS app2\nCOPY app2.txt /app2.txt\n", + wantOut: "Successfully created Dockerfile.raw\n", + wantOutFile: "Dockerfile.raw", + wantOutFileContent: `digraph G { + compound=true; + nodesep=1.00; + rankdir=LR; + ranksep=0.50; + external_image_0->stage_0; + external_image_0->stage_1; + external_image_0 [ color=grey20, fontcolor=grey20, label="scratch", shape=box, style="dashed,rounded", width=2 ]; + stage_0 [ label="app1", shape=box, style=rounded, width=2 ]; + stage_1 [ fillcolor=grey90, label="app2", shape=box, style="filled,rounded", width=2 ]; + +} +`, + }, + { + name: "scratch flag separated mode", + cliArgs: []string{"--scratch", "separated", "-o", "raw"}, dockerfileContent: "FROM scratch AS app1\nCOPY app1.txt /app1.txt\n\n" + "FROM scratch AS app2\nCOPY app2.txt /app2.txt\n", wantOut: "Successfully created Dockerfile.raw\n", @@ -513,6 +534,30 @@ It creates a visual graph representation of the build process. } `, }, + { + name: "scratch flag hidden mode", + cliArgs: []string{"--scratch", "hidden", "-o", "raw"}, + dockerfileContent: "FROM scratch AS app1\nCOPY app1.txt /app1.txt\n\n" + + "FROM scratch AS app2\nCOPY app2.txt /app2.txt\n", + wantOut: "Successfully created Dockerfile.raw\n", + wantOutFile: "Dockerfile.raw", + wantOutFileContent: `digraph G { + compound=true; + nodesep=1.00; + rankdir=LR; + ranksep=0.50; + stage_0 [ label="app1", shape=box, style=rounded, width=2 ]; + stage_1 [ fillcolor=grey90, label="app2", shape=box, style="filled,rounded", width=2 ]; + +} +`, + }, + { + name: "scratch flag invalid mode", + cliArgs: []string{"--scratch", "invalid", "-o", "raw"}, + wantErr: true, + wantOutRegex: `invalid argument "invalid" for "--scratch" flag: invalid value: invalid`, + }, } for _, tt := range tests { @@ -561,40 +606,13 @@ It creates a visual graph representation of the build process. } }) - // Cleanup + // Cleanup output files written to real filesystem if tt.wantOutFile != "" { os.Remove(tt.wantOutFile) } } } -func TestExecute(t *testing.T) { - tests := []test{ - { - name: "should work", - wantOutFile: "Dockerfile.pdf", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _ = os.WriteFile("Dockerfile", []byte(dockerfileContent), 0644) - - cmd.Execute() - - if tt.wantOutFile != "" { - _, err := os.Stat(tt.wantOutFile) - if err != nil { - t.Errorf("%s: %v", tt.name, err) - } - } - - // Cleanup - os.Remove("Dockerfile") - os.Remove(tt.wantOutFile) - }) - } -} - func checkWantOut(t *testing.T, tt test, buf *bytes.Buffer) { if tt.wantOut == "" && tt.wantOutRegex == "" { t.Fatalf("Either wantOut or wantOutRegex must be set") diff --git a/internal/dockerfile2dot/convert.go b/internal/dockerfile2dot/convert.go index 5c3b2013..75593b36 100644 --- a/internal/dockerfile2dot/convert.go +++ b/internal/dockerfile2dot/convert.go @@ -57,7 +57,7 @@ func newLayer( func dockerfileToSimplifiedDockerfile( content []byte, maxLabelLength int, - separateScratch bool, + scratchMode string, ) (simplifiedDockerfile SimplifiedDockerfile, err error) { result, err := parser.Parse(bytes.NewReader(content)) if err != nil { @@ -77,26 +77,11 @@ func dockerfileToSimplifiedDockerfile( case instructionFrom: // Create a new stage stageIndex++ - stage := Stage{} - - // If there is an "AS" alias, set it as the name - if node.Next.Next != nil { - stage.Name = node.Next.Next.Next.Value - stages[stage.Name] = struct{}{} - } - + stage, layer := processFromInstruction(node, argReplacements, maxLabelLength, scratchMode, stages) simplifiedDockerfile.Stages = append(simplifiedDockerfile.Stages, stage) // Add a new layer layerIndex = 0 - layer := newLayer(node, argReplacements, maxLabelLength) - - // Set the waitFor ID - layer.WaitFors = []WaitFor{{ - ID: replaceArgVars(node.Next.Value, argReplacements), - Type: waitForType(waitForFrom), - }} - simplifiedDockerfile.Stages[stageIndex].Layers = append( simplifiedDockerfile.Stages[stageIndex].Layers, layer, @@ -105,19 +90,7 @@ func dockerfileToSimplifiedDockerfile( case instructionCopy: // Add a new layer layerIndex++ - layer := newLayer(node, argReplacements, maxLabelLength) - - // If there is a "--from" option, set the waitFor ID - for _, flag := range node.Flags { - result := fromFlagRegex.FindSubmatch([]byte(flag)) - if len(result) > 1 { - layer.WaitFors = []WaitFor{{ - ID: string(result[1]), - Type: waitForType(waitForCopy), - }} - } - } - + layer := processCopyInstruction(node, argReplacements, maxLabelLength, scratchMode) simplifiedDockerfile.Stages[stageIndex].Layers = append( simplifiedDockerfile.Stages[stageIndex].Layers, layer, @@ -126,21 +99,7 @@ func dockerfileToSimplifiedDockerfile( case instructionRun: // Add a new layer layerIndex++ - layer := newLayer(node, argReplacements, maxLabelLength) - - // If there is a "--mount=(.*)from=..." option, set the waitFor ID - for _, flag := range node.Flags { - matches := mountFlagRegex.FindAllSubmatch([]byte(flag), -1) - for _, match := range matches { - if len(match) > 1 { - layer.WaitFors = append(layer.WaitFors, WaitFor{ - ID: string(match[1]), - Type: waitForType(waitForMount), - }) - } - } - } - + layer := processRunInstruction(node, argReplacements, maxLabelLength, scratchMode) simplifiedDockerfile.Stages[stageIndex].Layers = append( simplifiedDockerfile.Stages[stageIndex].Layers, layer, @@ -149,26 +108,17 @@ func dockerfileToSimplifiedDockerfile( default: // Add a new layer layerIndex++ - layer := newLayer(node, argReplacements, maxLabelLength) if stageIndex == -1 { + layer := processBeforeFirstStage(node, &argReplacements, maxLabelLength) simplifiedDockerfile.BeforeFirstStage = append( simplifiedDockerfile.BeforeFirstStage, layer, ) - - // NOTE: Currently, only global ARGs (defined before the first FROM instruction) - // are processed for variable substitution. Stage-specific ARGs are not yet fully supported. - if strings.ToUpper(node.Value) == instructionArg { - key, value, valueProvided := strings.Cut(node.Next.Value, "=") - if valueProvided { - argReplacements = appendAndResolveArgReplacement(argReplacements, ArgReplacement{Key: key, Value: value}) - } - } - break } + layer := newLayer(node, argReplacements, maxLabelLength) simplifiedDockerfile.Stages[stageIndex].Layers = append( simplifiedDockerfile.Stages[stageIndex].Layers, layer, @@ -176,16 +126,125 @@ func dockerfileToSimplifiedDockerfile( } } - addExternalImages(&simplifiedDockerfile, stages, separateScratch) + addExternalImages(&simplifiedDockerfile, stages, scratchMode) return } +// shouldSkipScratchWaitFor returns true if scratch WaitFors should be skipped in hidden mode +func shouldSkipScratchWaitFor(scratchMode, waitForID string) bool { + return scratchMode == "hidden" && waitForID == "scratch" +} + +// processFromInstruction handles FROM instruction parsing +func processFromInstruction( + node *parser.Node, + argReplacements []ArgReplacement, + maxLabelLength int, + scratchMode string, + stages map[string]struct{}, +) (Stage, Layer) { + stage := Stage{} + + // If there is an "AS" alias, set it as the name + if node.Next.Next != nil { + stage.Name = node.Next.Next.Next.Value + stages[stage.Name] = struct{}{} + } + + layer := newLayer(node, argReplacements, maxLabelLength) + + // Set the waitFor ID (skip scratch in hidden mode) + waitForID := replaceArgVars(node.Next.Value, argReplacements) + if !shouldSkipScratchWaitFor(scratchMode, waitForID) { + layer.WaitFors = []WaitFor{{ + ID: waitForID, + Type: waitForType(waitForFrom), + }} + } + + return stage, layer +} + +// processCopyInstruction handles COPY instruction parsing +func processCopyInstruction( + node *parser.Node, + argReplacements []ArgReplacement, + maxLabelLength int, + scratchMode string, +) Layer { + layer := newLayer(node, argReplacements, maxLabelLength) + + // If there is a "--from" option, set the waitFor ID (skip scratch in hidden mode) + for _, flag := range node.Flags { + result := fromFlagRegex.FindSubmatch([]byte(flag)) + if len(result) > 1 { + fromID := string(result[1]) + if !shouldSkipScratchWaitFor(scratchMode, fromID) { + layer.WaitFors = []WaitFor{{ + ID: fromID, + Type: waitForType(waitForCopy), + }} + } + } + } + + return layer +} + +// processRunInstruction handles RUN instruction parsing +func processRunInstruction( + node *parser.Node, + argReplacements []ArgReplacement, + maxLabelLength int, + scratchMode string, +) Layer { + layer := newLayer(node, argReplacements, maxLabelLength) + + // If there is a "--mount=(.*)from=..." option, set the waitFor ID (skip scratch in hidden mode) + for _, flag := range node.Flags { + matches := mountFlagRegex.FindAllSubmatch([]byte(flag), -1) + for _, match := range matches { + if len(match) > 1 { + mountID := string(match[1]) + if !shouldSkipScratchWaitFor(scratchMode, mountID) { + layer.WaitFors = append(layer.WaitFors, WaitFor{ + ID: mountID, + Type: waitForType(waitForMount), + }) + } + } + } + } + + return layer +} + +// processBeforeFirstStage handles instructions before the first FROM +func processBeforeFirstStage( + node *parser.Node, + argReplacements *[]ArgReplacement, + maxLabelLength int, +) Layer { + layer := newLayer(node, *argReplacements, maxLabelLength) + + // NOTE: Currently, only global ARGs (defined before the first FROM instruction) + // are processed for variable substitution. Stage-specific ARGs are not yet fully supported. + if strings.ToUpper(node.Value) == instructionArg { + key, value, valueProvided := strings.Cut(node.Next.Value, "=") + if valueProvided { + *argReplacements = appendAndResolveArgReplacement(*argReplacements, ArgReplacement{Key: key, Value: value}) + } + } + + return layer +} + // addExternalImages processes all layers and identifies external images. func addExternalImages( simplifiedDockerfile *SimplifiedDockerfile, stages map[string]struct{}, - separateScratch bool, + scratchMode string, ) { // Counter to generate unique IDs for separate scratch instances scratchCounter := 0 @@ -193,50 +252,58 @@ func addExternalImages( // Iterate through all stages and layers to find external image dependencies for stageIndex, stage := range simplifiedDockerfile.Stages { for layerIndex, layer := range stage.Layers { + // Process WaitFors for external image collection for waitForIndex, waitFor := range layer.WaitFors { - // Skip if this references an internal stage (not an external image) if _, ok := stages[waitFor.ID]; ok { continue } - // Start with the original image reference as both ID and name imageID := waitFor.ID originalName := waitFor.ID - // Handle scratch image separation: generate unique IDs while preserving display name - if originalName == "scratch" && separateScratch { - imageID = fmt.Sprintf("scratch-%d", scratchCounter) - scratchCounter++ - // Update the layer's waitFor reference to use the unique ID for graph connections - simplifiedDockerfile.Stages[stageIndex].Layers[layerIndex].WaitFors[waitForIndex].ID = imageID - } - - // Avoid duplicate external image entries (based on unique ID) - externalImageAlreadyAdded := false - for _, externalImage := range simplifiedDockerfile.ExternalImages { - if externalImage.ID == imageID { - externalImageAlreadyAdded = true - break + // Handle scratch image modes + if originalName == "scratch" { + switch scratchMode { + case "separated": + // Generate unique IDs while preserving display name + imageID = fmt.Sprintf("scratch-%d", scratchCounter) + scratchCounter++ + // Update the layer's waitFor reference to use the unique ID for graph connections + simplifiedDockerfile.Stages[stageIndex].Layers[layerIndex].WaitFors[waitForIndex].ID = imageID + case "hidden": + // Skip adding to external images entirely + continue + case "collapsed": + // Default behavior - use original ID (no changes needed) + default: + // Default to collapsed for unknown modes } } - if externalImageAlreadyAdded { - continue - } - // Add the external image with proper ID/Name separation - simplifiedDockerfile.ExternalImages = append( - simplifiedDockerfile.ExternalImages, - ExternalImage{ - ID: imageID, // Unique identifier for graph connections - Name: originalName, // Display name for graph labels - }, - ) + // Add to external images if not already present + addExternalImageIfNotExists(&simplifiedDockerfile.ExternalImages, imageID, originalName) } } } } +// addExternalImageIfNotExists adds an external image if it doesn't already exist +func addExternalImageIfNotExists(externalImages *[]ExternalImage, imageID, originalName string) { + // Avoid duplicate external image entries (based on unique ID) + for _, externalImage := range *externalImages { + if externalImage.ID == imageID { + return // Already exists + } + } + + // Add the external image with proper ID/Name separation + *externalImages = append(*externalImages, ExternalImage{ + ID: imageID, // Unique identifier for graph connections + Name: originalName, // Display name for graph labels + }) +} + // appendAndResolveArgReplacement appends a new ARG and resolves its value using already-resolved previous ARGs. func appendAndResolveArgReplacement( argReplacements []ArgReplacement, diff --git a/internal/dockerfile2dot/convert_test.go b/internal/dockerfile2dot/convert_test.go index 0beb7ea0..ffa0e298 100644 --- a/internal/dockerfile2dot/convert_test.go +++ b/internal/dockerfile2dot/convert_test.go @@ -8,9 +8,9 @@ import ( func Test_dockerfileToSimplifiedDockerfile(t *testing.T) { type args struct { - content []byte - maxLabelLength int - separateScratch bool + content []byte + maxLabelLength int + scratchMode string } tests := []struct { name string @@ -20,9 +20,9 @@ func Test_dockerfileToSimplifiedDockerfile(t *testing.T) { { name: "Most minimal Dockerfile", args: args{ - content: []byte("FROM scratch"), - maxLabelLength: 20, - separateScratch: false, + content: []byte("FROM scratch"), + maxLabelLength: 20, + scratchMode: "collapsed", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -50,8 +50,8 @@ FROM scratch COPY --from=base . . RUN --mount=type=cache,from=buildcache,source=/go/pkg/mod/cache/,target=/go/pkg/mod/cache/ go build `), - maxLabelLength: 20, - separateScratch: false, + maxLabelLength: 20, + scratchMode: "collapsed", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -98,8 +98,8 @@ RUN \ --mount=type=cache,from=buildcache,source=/go/pkg/mod/cache/,target=/go/pkg/mod/cache/ \ --mount=from=artifacts,source=/artifacts/embeddata,target=/artifacts/embeddata go build `), - maxLabelLength: 20, - separateScratch: false, + maxLabelLength: 20, + scratchMode: "collapsed", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -409,7 +409,7 @@ FROM $IMAGE2 }, }, { - name: "Separate scratch images", + name: "Scratch mode separated - multiple scratch images", args: args{ content: []byte(` FROM scratch AS app1 @@ -418,8 +418,8 @@ COPY app1.txt /app1.txt FROM scratch AS app2 COPY app2.txt /app2.txt `), - maxLabelLength: 20, - separateScratch: true, + maxLabelLength: 20, + scratchMode: "separated", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -451,12 +451,12 @@ COPY app2.txt /app2.txt }, }, { - name: "Single scratch image with separate flag", + name: "Scratch mode separated - single scratch image", args: args{ content: []byte(`FROM scratch AS app COPY app.txt /app.txt`), - maxLabelLength: 20, - separateScratch: true, + maxLabelLength: 20, + scratchMode: "separated", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -477,15 +477,15 @@ COPY app.txt /app.txt`), }, }, { - name: "No scratch images with separate flag enabled", + name: "Scratch mode separated - no scratch images", args: args{ content: []byte(`FROM ubuntu AS base COPY app.txt /app.txt FROM alpine AS final COPY --from=base /app.txt /final.txt`), - maxLabelLength: 20, - separateScratch: true, + maxLabelLength: 20, + scratchMode: "separated", }, want: SimplifiedDockerfile{ ExternalImages: []ExternalImage{ @@ -519,13 +519,89 @@ COPY --from=base /app.txt /final.txt`), }, }, }, + { + name: "Scratch mode collapsed - multiple scratch images", + args: args{ + content: []byte(`FROM scratch AS app1 +COPY app1.txt /app1.txt + +FROM scratch AS app2 +COPY app2.txt /app2.txt`), + maxLabelLength: 20, + scratchMode: "collapsed", + }, + want: SimplifiedDockerfile{ + ExternalImages: []ExternalImage{ + {ID: "scratch", Name: "scratch"}, + }, + Stages: []Stage{ + { + Name: "app1", + Layers: []Layer{ + { + Label: "FROM scratch AS app1", + WaitFors: []WaitFor{{ID: "scratch", Type: waitForType(waitForFrom)}}, + }, + {Label: "COPY app1.txt /ap..."}, + }, + }, + { + Name: "app2", + Layers: []Layer{ + { + Label: "FROM scratch AS app2", + WaitFors: []WaitFor{{ID: "scratch", Type: waitForType(waitForFrom)}}, + }, + {Label: "COPY app2.txt /ap..."}, + }, + }, + }, + }, + }, + { + name: "Scratch mode hidden - multiple scratch images", + args: args{ + content: []byte(`FROM scratch AS app1 +COPY app1.txt /app1.txt + +FROM scratch AS app2 +COPY app2.txt /app2.txt`), + maxLabelLength: 20, + scratchMode: "hidden", + }, + want: SimplifiedDockerfile{ + ExternalImages: nil, + Stages: []Stage{ + { + Name: "app1", + Layers: []Layer{ + { + Label: "FROM scratch AS app1", + WaitFors: nil, + }, + {Label: "COPY app1.txt /ap..."}, + }, + }, + { + Name: "app2", + Layers: []Layer{ + { + Label: "FROM scratch AS app2", + WaitFors: nil, + }, + {Label: "COPY app2.txt /ap..."}, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := dockerfileToSimplifiedDockerfile( tt.args.content, tt.args.maxLabelLength, - tt.args.separateScratch, + tt.args.scratchMode, ) if err != nil { t.Errorf("dockerfileToSimplifiedDockerfile() error = %v", err) diff --git a/internal/dockerfile2dot/load.go b/internal/dockerfile2dot/load.go index 215c1061..44845887 100644 --- a/internal/dockerfile2dot/load.go +++ b/internal/dockerfile2dot/load.go @@ -16,7 +16,7 @@ func LoadAndParseDockerfile( inputFS afero.Fs, filename string, maxLabelLength int, - separateScratch bool, + scratchMode string, ) (SimplifiedDockerfile, error) { content, err := afero.ReadFile(inputFS, filename) if err != nil { @@ -29,5 +29,5 @@ func LoadAndParseDockerfile( } panic(err) } - return dockerfileToSimplifiedDockerfile(content, maxLabelLength, separateScratch) + return dockerfileToSimplifiedDockerfile(content, maxLabelLength, scratchMode) } diff --git a/internal/dockerfile2dot/load_test.go b/internal/dockerfile2dot/load_test.go index ec6d1183..60a968e0 100644 --- a/internal/dockerfile2dot/load_test.go +++ b/internal/dockerfile2dot/load_test.go @@ -54,8 +54,8 @@ func TestLoadAndParseDockerfile(t *testing.T) { _, err := LoadAndParseDockerfile( tt.args.inputFS, tt.args.filename, - 20, // Default maxLabelLength - false, // Default separateScratch + 20, // Default maxLabelLength + "collapsed", // Default scratchMode ) if (err != nil) != tt.wantErr { t.Errorf("LoadAndParseDockerfile() error = %v, wantErr %v", err, tt.wantErr)