diff --git a/cmd/anchor/root.go b/cmd/anchor/root.go index 833e3cc..761ec79 100644 --- a/cmd/anchor/root.go +++ b/cmd/anchor/root.go @@ -2,10 +2,13 @@ package anchor import ( "bufio" + "context" "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" "github.com/fatih/color" "github.com/moby/buildkit/frontend/dockerfile/parser" @@ -41,6 +44,14 @@ var rootCmd = &cobra.Command{ SilenceUsage: true, SilenceErrors: true, RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithCancel(context.Background()) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + cancel() + os.Exit(1) + }() if anchor.IsDockerInstalled() { if !anchor.IsDockerRunning() { @@ -78,12 +89,25 @@ var rootCmd = &cobra.Command{ } appendArch := len(options.Architectures) > 1 + content, err := os.Open(options.InputFile) + if err != nil { + return err + } + defer content.Close() + lines := []string{} + scanner := bufio.NewScanner(content) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return err + } for _, architecture := range options.Architectures { content, err := os.Open(options.InputFile) if err != nil { return err } - defer content.Close() result, err := parser.Parse(content) if err != nil { @@ -91,17 +115,16 @@ var rootCmd = &cobra.Command{ } node := result.AST - anchor.PrintNode(node) color.Cyan("Anchoring to architecture: %s\n", architecture) image := "" - err = anchor.ParseNode(node, architecture, &image) + err = anchor.ParseNode(ctx, node, architecture, &image) if err != nil { return err } var builder strings.Builder - anchor.WriteDockerfile(&builder, node, true) + anchor.WriteDockerfile(&builder, node, true, 0, lines) outputName := options.OutputFile if appendArch { outputName = fmt.Sprintf("%s.%s", outputName, architecture) diff --git a/docker.go b/docker.go index 907f7c9..bb1f607 100644 --- a/docker.go +++ b/docker.go @@ -1,6 +1,7 @@ package anchor import ( + "context" "fmt" "os/exec" "strings" @@ -28,7 +29,19 @@ func attachDockerSha(node *parser.Node) (string, error) { return node.Value, nil } -func WriteDockerfile(builder *strings.Builder, node *parser.Node, useOriginal bool) { +func WriteDockerfile(builder *strings.Builder, node *parser.Node, useOriginal bool, currentLine int, lines []string) int { + // this allows us to maintain things like comments and newlines in the original Dockerfile + if currentLine != 0 && node.StartLine != 0 && currentLine < node.StartLine { + for i := currentLine + 1; i < node.StartLine; i++ { + builder.WriteString(lines[i-1]) + builder.WriteString("\n") + } + } + if currentLine == 0 { + currentLine = 1 + } else if node.EndLine != 0 { + currentLine = node.EndLine + } if node.Value == "FROM" || node.Value == "RUN" { useOriginal = false } @@ -46,27 +59,18 @@ func WriteDockerfile(builder *strings.Builder, node *parser.Node, useOriginal bo builder.WriteString(s) } for _, child := range node.Children { - WriteDockerfile(builder, child, useOriginal) - builder.WriteString("\n\n") + currentLine = WriteDockerfile(builder, child, useOriginal, currentLine, lines) + builder.WriteString("\n") } if node.Next != nil { builder.WriteString(" ") - WriteDockerfile(builder, node.Next, useOriginal) - } -} - -func PrintNode(node *parser.Node) { - for _, child := range node.Children { - PrintNode(child) - } - - if node.Next != nil { - PrintNode(node.Next) + currentLine = WriteDockerfile(builder, node.Next, useOriginal, currentLine, lines) } + return currentLine } -func ParseNode(node *parser.Node, architecture string, image *string) error { +func ParseNode(ctx context.Context, node *parser.Node, architecture string, image *string) error { if node == nil { return nil } @@ -79,16 +83,16 @@ func ParseNode(node *parser.Node, architecture string, image *string) error { } *image = newImage } else if node.Value == "RUN" { - err := parseRunCommand(node.Next, architecture, *image) + err := parseRunCommand(ctx, node.Next, architecture, *image) if err != nil { return err } } else if node.Next != nil { - ParseNode(node.Next, architecture, image) + ParseNode(ctx, node.Next, architecture, image) } for _, child := range node.Children { - ParseNode(child, architecture, image) + ParseNode(ctx, child, architecture, image) } return nil } diff --git a/packages.go b/packages.go index 990dc64..3220c52 100644 --- a/packages.go +++ b/packages.go @@ -2,6 +2,7 @@ package anchor import ( "bytes" + "context" "fmt" "os/exec" "strings" @@ -10,16 +11,14 @@ import ( ) func fetchPackageVersions( - packages []string, - architecture string, - image string, + ctx context.Context, packages []string, architecture string, image string, ) (map[string]string, error) { var stdoutBuf, stderrBuf bytes.Buffer command := "dpkg --add-architecture " + architecture + " && apt-get update && apt-cache show --" for _, pkg := range packages { command += " " + pkg + ":" + architecture } - c := exec.Command("docker", "run", "--rm", image, "bash", "-c", command) // #nosec G204 + c := exec.CommandContext(ctx, "docker", "run", "--rm", image, "bash", "-c", command) // #nosec G204 c.Stdout = &stdoutBuf c.Stderr = &stderrBuf // Use a buffer to capture stderr output @@ -97,7 +96,7 @@ func parseCommand(command string) []string { return packages } -func parseRunCommand(node *parser.Node, architecture string, image string) error { +func parseRunCommand(ctx context.Context, node *parser.Node, architecture string, image string) error { if node == nil { return nil } @@ -108,7 +107,7 @@ func parseRunCommand(node *parser.Node, architecture string, image string) error if len(packageNames) == 0 { continue } - packageMap, err := fetchPackageVersions(packageNames, architecture, image) + packageMap, err := fetchPackageVersions(ctx, packageNames, architecture, image) if err != nil { return err }