Skip to content

Commit

Permalink
Add --max-label-length flag (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhoefler authored Jan 30, 2023
1 parent 7a81faf commit 8250ce0
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 129 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,17 @@ Usage:
dockerfilegraph [flags]
Flags:
-c, --concentrate concentrate the edges (default false)
-d, --dpi uint dots per inch of the PNG export (default 96)
-e, --edgestyle style of the graph edges, one of: default, solid (default default)
-f, --filename string name of the Dockerfile (default "Dockerfile")
-h, --help help for dockerfilegraph
--layers display all layers (default false)
--legend add a legend (default false)
-o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf)
-u, --unflatten uint stagger length of leaf edges between [1,u] (default 0)
--version display the version of dockerfilegraph
-c, --concentrate concentrate the edges (default false)
-d, --dpi uint dots per inch of the PNG export (default 96)
-e, --edgestyle style of the graph edges, one of: default, solid (default default)
-f, --filename string name of the Dockerfile (default "Dockerfile")
-h, --help help for dockerfilegraph
--layers display all layers (default false)
--legend add a legend (default false)
-m, --max-label-length uint maximum length of the node labels, must be at least 4 (default 20)
-o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf)
-u, --unflatten uint stagger length of leaf edges between [1,u] (default 0)
--version display the version of dockerfilegraph
```

## License
Expand Down
60 changes: 46 additions & 14 deletions examples/dockerfiles/Dockerfile.large
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
FROM scratch AS base
COPY --from=external_0 file_0_0 /
COPY --from=external_1 file_1_0 /
COPY --from=external_2 file_2_0 /
COPY --from=external_3 file_3_0 /
COPY --from=external_4 file_4_0 /
COPY --from=external_5 file_5_0 /
COPY --from=external_6 file_6_0 /
FROM --platform=linux/amd64 ubuntu:22.04 AS ubuntu_amd64

FROM base AS build
RUN --mount=type=cache,from=buildcache,source=/build,target=/build make build
FROM --platform=linux/arm64 ubuntu:22.04 AS ubuntu_arm64

FROM build AS final
COPY --from=supplemental file_supplemental_0 /
COPY --from=supplemental file_supplemental_1 /
COPY --from=supplemental file_supplemental_2 /
FROM ubuntu:22.04 AS ubuntu_with_amd64_emulation
COPY --from=ubuntu_amd64 /etc/apt/sources.list /etc/apt/sources.list.amd64
COPY --from=ubuntu_arm64 /etc/apt/sources.list /etc/apt/sources.list.arm64
COPY --from=ubuntu_amd64 /usr/bin/cat ./cat
COPY --from=ubuntu_arm64 /usr/bin/cat ./cat

FROM alpine:3.12.0 AS a_dependency

FROM ubuntu_with_amd64_emulation AS base
COPY --from=a_dependency /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.1 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.2 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.3 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.4 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.5 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.6 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.7 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.14.8 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.1 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.2 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.3 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.4 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.5 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.15.6 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.16.1 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.16.2 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.16.3 /bin/cat /tmp/cat
COPY --from=docker.io/alpine:3.16 /bin/cat /tmp/cat

FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS build_buildplatform
RUN --mount=type=cache,target=/root/.m2/repository echo mvn dependencies ...
RUN --mount=type=cache,target=/root/.m2/repository echo mvn package ...

FROM base as test
COPY --from=build_buildplatform /bin/cat /tmp/cat
COPY --from=build_buildplatform /bin/cat /tmp/cat
COPY --from=build_buildplatform /bin/cat /tmp/cat
RUN --mount=type=cache,target=/root/.m2/repository echo mvn verify ...

FROM base
COPY --from=build_buildplatform /bin/cat /tmp/cat
COPY --from=build_buildplatform /bin/cat /tmp/cat
53 changes: 39 additions & 14 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ import (
)

var (
concentrateFlag bool
dpiFlag uint
edgeStyleFlag enum
filenameFlag string
layersFlag bool
legendFlag bool
outputFlag enum
unflattenFlag uint
versionFlag bool
concentrateFlag bool
dpiFlag uint
edgestyleFlag enum
filenameFlag string
layersFlag bool
legendFlag bool
maxLabelLengthFlag uint
outputFlag enum
unflattenFlag uint
versionFlag bool
)

// dfgWriter is a writer that prints to stdout. When testing, we
Expand All @@ -43,12 +44,19 @@ func NewRootCmd(dfgWriter io.Writer, inputFS afero.Fs) *cobra.Command {
Long: `dockerfilegraph visualizes your multi-stage Dockerfile.
It outputs a graph representation of the build process.`,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
return checkFlags()
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
if versionFlag {
return printVersion(dfgWriter)
}

dockerfile, err := dockerfile2dot.LoadAndParseDockerfile(inputFS, filenameFlag)
dockerfile, err := dockerfile2dot.LoadAndParseDockerfile(
inputFS,
filenameFlag,
int(maxLabelLengthFlag),
)
if err != nil {
return
}
Expand All @@ -62,9 +70,10 @@ It outputs a graph representation of the build process.`,
dotFileContent := dockerfile2dot.BuildDotFile(
dockerfile,
concentrateFlag,
edgeStyleFlag.String(),
edgestyleFlag.String(),
layersFlag,
legendFlag,
int(maxLabelLengthFlag),
)

_, err = dotFile.Write([]byte(dotFileContent))
Expand Down Expand Up @@ -167,12 +176,12 @@ It outputs a graph representation of the build process.`,
"dots per inch of the PNG export",
)

edgeStyleFlag = newEnum("default", "solid")
edgestyleFlag = newEnum("default", "solid")
rootCmd.Flags().VarP(
&edgeStyleFlag,
&edgestyleFlag,
"edgestyle",
"e",
"style of the graph edges, one of: "+strings.Join(edgeStyleFlag.AllowedValues(), ", "),
"style of the graph edges, one of: "+strings.Join(edgestyleFlag.AllowedValues(), ", "),
)

rootCmd.Flags().StringVarP(
Expand All @@ -197,6 +206,14 @@ It outputs a graph representation of the build process.`,
"add a legend (default false)",
)

rootCmd.Flags().UintVarP(
&maxLabelLengthFlag,
"max-label-length",
"m",
20,
"maximum length of the node labels, must be at least 4",
)

outputFlag = newEnum("pdf", "canon", "dot", "png", "raw", "svg")
rootCmd.Flags().VarP(
&outputFlag,
Expand Down Expand Up @@ -231,3 +248,11 @@ func Execute() {
os.Exit(1)
}
}

func checkFlags() (err error) {
if maxLabelLengthFlag < 4 {
err = fmt.Errorf("--max-label-length must be at least 4")
return
}
return
}
27 changes: 17 additions & 10 deletions internal/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ var usage = `Usage:
dockerfilegraph [flags]
Flags:
-c, --concentrate concentrate the edges (default false)
-d, --dpi uint dots per inch of the PNG export (default 96)
-e, --edgestyle style of the graph edges, one of: default, solid (default default)
-f, --filename string name of the Dockerfile (default "Dockerfile")
-h, --help help for dockerfilegraph
--layers display all layers (default false)
--legend add a legend (default false)
-o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf)
-u, --unflatten uint stagger length of leaf edges between [1,u] (default 0)
--version display the version of dockerfilegraph
-c, --concentrate concentrate the edges (default false)
-d, --dpi uint dots per inch of the PNG export (default 96)
-e, --edgestyle style of the graph edges, one of: default, solid (default default)
-f, --filename string name of the Dockerfile (default "Dockerfile")
-h, --help help for dockerfilegraph
--layers display all layers (default false)
--legend add a legend (default false)
-m, --max-label-length uint maximum length of the node labels, must be at least 4 (default 20)
-o, --output output file format, one of: canon, dot, pdf, png, raw, svg (default pdf)
-u, --unflatten uint stagger length of leaf edges between [1,u] (default 0)
--version display the version of dockerfilegraph
`

// Taken from example/Dockerfile.
Expand Down Expand Up @@ -99,6 +100,12 @@ It outputs a graph representation of the build process.
wantErr: true,
wantOut: "Error: file with no instructions\n" + usage + "\n",
},
{
name: "--max-label-length too small",
cliArgs: []string{"--max-label-length", "3"},
wantErr: true,
wantOut: "Error: --max-label-length must be at least 4\n" + usage + "\n",
},
{
name: "output flag dot",
cliArgs: []string{"--output", "dot"},
Expand Down
48 changes: 24 additions & 24 deletions internal/dockerfile2dot/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import (
"github.com/awalterschulze/gographviz"
)

const maxLabelLength = 20

// BuildDotFile builds a GraphViz .dot file from a simplified Dockerfile
func BuildDotFile(
simplifiedDockerfile SimplifiedDockerfile,
concentrate bool,
edgeStyle string,
edgestyle string,
layers bool,
legend bool,
maxLabelLength int,
) string {
// Create a new graph
graph := gographviz.NewEscape()
Expand All @@ -31,15 +30,18 @@ func BuildDotFile(

// Add the legend if requested
if legend {
addLegend(graph, edgeStyle)
addLegend(graph, edgestyle)
}

// Add the external images
for externalImageIndex, externalImage := range simplifiedDockerfile.ExternalImages {

label := externalImage.Name
if len(label) > maxLabelLength {
label = truncate.Truncate(label, maxLabelLength, "...", truncate.PositionMiddle)
truncatePosition := truncate.PositionMiddle
if maxLabelLength < 5 {
truncatePosition = truncate.PositionEnd
}
label = truncate.Truncate(label, maxLabelLength, "...", truncatePosition)
}

_ = graph.AddNode(
Expand All @@ -64,17 +66,6 @@ func BuildDotFile(
"width": "2",
}

// Add the build stages if layers are not requested
if !layers {
// Color the last stage, because it is the default build target
if stageIndex == len(simplifiedDockerfile.Stages)-1 {
attrs["style"] = "\"filled,rounded\""
attrs["fillcolor"] = "grey90"
}

_ = graph.AddNode("G", fmt.Sprintf("stage_%d", stageIndex), attrs)
}

// Add layers if requested
if layers {
cluster := fmt.Sprintf("cluster_stage_%d", stageIndex)
Expand Down Expand Up @@ -112,11 +103,20 @@ func BuildDotFile(
)
}
}
} else {
// Add the build stages.
// Color the last one, because it is the default build target.
if stageIndex == len(simplifiedDockerfile.Stages)-1 {
attrs["style"] = "\"filled,rounded\""
attrs["fillcolor"] = "grey90"
}

_ = graph.AddNode("G", fmt.Sprintf("stage_%d", stageIndex), attrs)
}

// Add the egdes for this build stage
addEdgesForStage(
stageIndex, stage, graph, simplifiedDockerfile, layers, edgeStyle,
stageIndex, stage, graph, simplifiedDockerfile, layers, edgestyle,
)
}

Expand Down Expand Up @@ -148,7 +148,7 @@ func BuildDotFile(

func addEdgesForStage(
stageIndex int, stage Stage, graph *gographviz.Escape,
simplifiedDockerfile SimplifiedDockerfile, layers bool, edgeStyle string,
simplifiedDockerfile SimplifiedDockerfile, layers bool, edgestyle string,
) {
for layerIndex, layer := range stage.Layers {
if layer.WaitFor.Name == "" {
Expand All @@ -158,12 +158,12 @@ func addEdgesForStage(
edgeAttrs := map[string]string{}
if layer.WaitFor.Type == waitForType(copy) {
edgeAttrs["arrowhead"] = "empty"
if edgeStyle == "default" {
if edgestyle == "default" {
edgeAttrs["style"] = "dashed"
}
} else if layer.WaitFor.Type == waitForType(runMountTypeCache) {
edgeAttrs["arrowhead"] = "ediamond"
if edgeStyle == "default" {
if edgestyle == "default" {
edgeAttrs["style"] = "dotted"
}
}
Expand All @@ -184,7 +184,7 @@ func addEdgesForStage(
}
}

func addLegend(graph *gographviz.Escape, edgeStyle string) {
func addLegend(graph *gographviz.Escape, edgestyle string) {
_ = graph.AddSubGraph("G", "cluster_legend", nil)

_ = graph.AddNode("cluster_legend", "key",
Expand Down Expand Up @@ -215,7 +215,7 @@ func addLegend(graph *gographviz.Escape, edgeStyle string) {
_ = graph.AddPortEdge("key", "i0:e", "key2", "i0:w", true, nil)

copyEdgeAttrs := map[string]string{"arrowhead": "empty"}
if edgeStyle == "default" {
if edgestyle == "default" {
copyEdgeAttrs["style"] = "dashed"
}
_ = graph.AddPortEdge(
Expand All @@ -224,7 +224,7 @@ func addLegend(graph *gographviz.Escape, edgeStyle string) {
)

cacheEdgeAttrs := map[string]string{"arrowhead": "ediamond"}
if edgeStyle == "default" {
if edgestyle == "default" {
cacheEdgeAttrs["style"] = "dotted"
}
_ = graph.AddPortEdge(
Expand Down
Loading

0 comments on commit 8250ce0

Please sign in to comment.