Skip to content

Commit

Permalink
refactor: introduce Buider per Engine abstract, as preparation to add…
Browse files Browse the repository at this point in the history
… support for more engines
  • Loading branch information
Tomasz Gągor committed Dec 20, 2024
1 parent c90eecf commit 1c3dd7c
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 43 deletions.
2 changes: 2 additions & 0 deletions cmd/td/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ func init() {

cmd.Flags().BoolVarP(&flags.Build, "build", "b", false, "Build Docker images after templating")
cmd.Flags().StringVarP(&flags.Image, "image", "i", "", "Limit the build to a single image")
// FIXME: add more engines, add check for correctness, podman, buildx, kaniko)")
cmd.Flags().StringVarP(&flags.Engine, "engine", "e", "docker", "Select the container engine to use (docker, buiildx)")
cmd.Flags().BoolVarP(&flags.Push, "push", "p", false, "Push Docker images after building")
cmd.Flags().BoolVarP(&flags.Delete, "delete", "d", false, "Delete templated Dockerfiles after successful building")
cmd.Flags().BoolVarP(&flags.Squash, "squash", "s", false, "Squash images to reduce size (experimental)")
Expand Down
28 changes: 28 additions & 0 deletions pkg/builder/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package builder

type Builder interface {
// New(threads int, dryRun bool) *Builder
Init() error
Build(dockerfile, imageName string, labels map[string]string, contextDir string, verbose bool)
Tag(imageName, taggedImage string, verbose bool)
Push(taggedImage string, verbose bool)
Remove(imageName string, verbose bool)
Run(stage Stage) error
SetThreads(threads int)
SetDryRun(dryRun bool)
}

// func DefaultRun(b Builder, stage Stage) error {
// switch stage {
// case Build:
// return b.Build()
// case Tag:
// return b.Tag()
// case Push:
// return b.Push()
// case Remove:
// return b.Remove()
// default:
// return fmt.Errorf("unknown stage: %s", stage)
// }
// }
96 changes: 96 additions & 0 deletions pkg/builder/buildx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package builder

import (
"fmt"

"github.com/tgagor/template-dockerfiles/pkg/cmd"
"github.com/tgagor/template-dockerfiles/pkg/runner"
)

type BuildxBuilder struct {
buildTasks runner.Runner
tagTasks runner.Runner
pushTasks runner.Runner
cleanupTasks runner.Runner
}

func (b *BuildxBuilder) Init() error {
// create
// docker buildx create \
// --name multi-platform-builder \
// --driver docker-container \
// --use
return nil
}

func (b *BuildxBuilder) SetThreads(threads int) {
b.buildTasks = b.buildTasks.Threads(threads)
// b.tagTasks have to use 1 thread
b.pushTasks = b.pushTasks.Threads(threads)
b.cleanupTasks = b.cleanupTasks.Threads(threads)
}

func (b *BuildxBuilder) SetDryRun(dryRun bool) {
b.buildTasks = b.buildTasks.DryRun(dryRun)
b.tagTasks = b.tagTasks.DryRun(dryRun)
b.pushTasks = b.pushTasks.DryRun(dryRun)
b.cleanupTasks = b.cleanupTasks.DryRun(dryRun)
}

func (b *BuildxBuilder) Build(dockerfile, imageName string, labels map[string]string, contextDir string, verbose bool) {
builder := cmd.New("docker").Arg("buildx").Arg("build").
Arg("-f", dockerfile).
Arg("-t", imageName).
Arg(labelsToArgs(labels)...).
Arg(contextDir).
SetVerbose(verbose)
b.buildTasks = b.buildTasks.AddTask(builder)
}

func (b *BuildxBuilder) Tag(imageName, taggedImage string, verbose bool) {
tagger := cmd.New("docker").Arg("tag").
Arg(imageName).
Arg(taggedImage).
SetVerbose(verbose).
PreInfo("Tagging " + taggedImage)
b.tagTasks = b.tagTasks.AddTask(tagger)
}

func (b *BuildxBuilder) Push(taggedImage string, verbose bool) {
pusher := cmd.New("docker").Arg("push").
Arg(taggedImage)
if !verbose {
pusher = pusher.Arg("--quiet")
}
b.pushTasks = b.pushTasks.AddTask(pusher)
}

func (b *BuildxBuilder) Remove(imageName string, verbose bool) {
remover := cmd.New("docker").Arg("image", "rm", "-f").
Arg(imageName).
SetVerbose(verbose)
b.cleanupTasks = b.cleanupTasks.AddTask(remover)
}

func (b *BuildxBuilder) Run(stage Stage) error {
switch stage {
case Build:
return b.buildTasks.Run()
case Tag:
return b.tagTasks.Run()
case Push:
return b.pushTasks.Run()
case Remove:
return b.cleanupTasks.Run()
default:
return fmt.Errorf("unknown stage: %s", stage)
}
}

// func labelsToArgs(labels map[string]string) []string {
// args := []string{}
// for k, v := range labels {
// args = append(args, "--label", k+"="+v)
// }
// return args
// }
92 changes: 92 additions & 0 deletions pkg/builder/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package builder

import (
"fmt"

"github.com/tgagor/template-dockerfiles/pkg/cmd"
"github.com/tgagor/template-dockerfiles/pkg/runner"
)

type DockerBuilder struct {
buildTasks runner.Runner
tagTasks runner.Runner
pushTasks runner.Runner
cleanupTasks runner.Runner
}

func (b *DockerBuilder) Init() error {
return nil
}

func (b *DockerBuilder) SetThreads(threads int) {
b.buildTasks = b.buildTasks.Threads(threads)
// b.tagTasks have to use 1 thread
b.pushTasks = b.pushTasks.Threads(threads)
b.cleanupTasks = b.cleanupTasks.Threads(threads)
}

func (b *DockerBuilder) SetDryRun(dryRun bool) {
b.buildTasks = b.buildTasks.DryRun(dryRun)
b.tagTasks = b.tagTasks.DryRun(dryRun)
b.pushTasks = b.pushTasks.DryRun(dryRun)
b.cleanupTasks = b.cleanupTasks.DryRun(dryRun)
}

func (b *DockerBuilder) Build(dockerfile, imageName string, labels map[string]string, contextDir string, verbose bool) {
builder := cmd.New("docker").Arg("build").
Arg("-f", dockerfile).
Arg("-t", imageName).
Arg(labelsToArgs(labels)...).
Arg(contextDir).
SetVerbose(verbose)
b.buildTasks = b.buildTasks.AddTask(builder)
}

func (b *DockerBuilder) Tag(imageName, taggedImage string, verbose bool) {
tagger := cmd.New("docker").Arg("tag").
Arg(imageName).
Arg(taggedImage).
SetVerbose(verbose).
PreInfo("Tagging " + taggedImage)
b.tagTasks = b.tagTasks.AddTask(tagger)
}

func (b *DockerBuilder) Push(taggedImage string, verbose bool) {
pusher := cmd.New("docker").Arg("push").
Arg(taggedImage).
PreInfo("Pushing " + taggedImage)
if !verbose {
pusher = pusher.Arg("--quiet")
}
b.pushTasks = b.pushTasks.AddTask(pusher)
}

func (b *DockerBuilder) Remove(imageName string, verbose bool) {
remover := cmd.New("docker").Arg("image", "rm", "-f").
Arg(imageName).
SetVerbose(verbose)
b.cleanupTasks = b.cleanupTasks.AddTask(remover)
}

func (b *DockerBuilder) Run(stage Stage) error {
switch stage {
case Build:
return b.buildTasks.Run()
case Tag:
return b.tagTasks.Run()
case Push:
return b.pushTasks.Run()
case Remove:
return b.cleanupTasks.Run()
default:
return fmt.Errorf("unknown stage: %s", stage)
}
}

func labelsToArgs(labels map[string]string) []string {
args := []string{}
for k, v := range labels {
args = append(args, "--label", k+"="+v)
}
return args
}
14 changes: 14 additions & 0 deletions pkg/builder/stage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package builder

type Stage int

const (
Build Stage = iota
Tag
Push
Remove
)

func (s Stage) String() string {
return [...]string{"build", "tag", "push", "remove"}[s]
}
1 change: 1 addition & 0 deletions pkg/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type Flags struct {
Threads int
Verbose bool
Image string
Engine string
}
16 changes: 8 additions & 8 deletions pkg/parser/opencontainers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// Follow:
// https://github.com/opencontainers/image-spec/blob/main/annotations.md
func getOCILabels(cfg map[string]interface{}) map[string]string {
func collectOCILabels(cfg map[string]interface{}) map[string]string {
labels := map[string]string{}

if cfg["maintainer"] != "" {
Expand Down Expand Up @@ -45,15 +45,15 @@ func getOCILabels(cfg map[string]interface{}) map[string]string {
return labels
}

func labelsToArgs(labels map[string]string) []string {
args := []string{}
// func labelsToArgs(labels map[string]string) []string {
// args := []string{}

for k, v := range labels {
args = append(args, "--label", fmt.Sprintf("%s=%s", k, v))
}
// for k, v := range labels {
// args = append(args, "--label", fmt.Sprintf("%s=%s", k, v))
// }

return args
}
// return args
// }

func readGitRepo(path string) (originURL string, commitHex string, branchName string, err error) {
// Open the local git repository
Expand Down
58 changes: 23 additions & 35 deletions pkg/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/rs/zerolog/log"

"github.com/tgagor/template-dockerfiles/pkg/builder"
"github.com/tgagor/template-dockerfiles/pkg/cmd"
"github.com/tgagor/template-dockerfiles/pkg/config"
"github.com/tgagor/template-dockerfiles/pkg/runner"
Expand All @@ -37,12 +38,20 @@ func Run(workdir string, cfg *config.Config, flag config.Flags) error {
}

var toSquash []string
var buildEngine builder.Builder

// Choose the build engine based on the flag
switch flag.Engine {
case "buildx":
buildEngine = &builder.BuildxBuilder{}
// case "kaniko":
// buildEngine = &builder.KanikoBuilder{}
default:
buildEngine = &builder.DockerBuilder{}
}

buildTasks := runner.New().Threads(flag.Threads).DryRun(!flag.Build)
// labelling have to happen in order, so no parallelism
taggingTasks := runner.New().DryRun(!flag.Build)
pushTasks := runner.New().Threads(flag.Threads).DryRun(!flag.Build)
cleanupTasks := runner.New().Threads(flag.Threads).DryRun(!flag.Build)
buildEngine.SetThreads(flag.Threads)
buildEngine.SetDryRun(!flag.Build)

combinations := generateVariableCombinations(img.Variables)
for _, configSet := range combinations {
Expand Down Expand Up @@ -70,7 +79,7 @@ func Run(workdir string, cfg *config.Config, flag config.Flags) error {
tags := collectTags(img, configSet, name)

// Collect labels, starting with global labels, then oci, then per image
labels := getOCILabels(configSet)
labels := collectOCILabels(configSet)
maps.Copy(labels, collectLabels(configSet))

var dockerfile string
Expand All @@ -95,13 +104,7 @@ func Run(workdir string, cfg *config.Config, flag config.Flags) error {
currentImage := strings.Join([]string{name, generateCombinationString(configSet)}, "-")

// collect building image commands
builder := cmd.New("docker").Arg("build").
Arg("-f", dockerfile).
Arg("-t", currentImage).
Arg(labelsToArgs(labels)...).
Arg(filepath.Dir(dockerfileTemplate)).
SetVerbose(flag.Verbose)
buildTasks = buildTasks.AddTask(builder)
buildEngine.Build(dockerfile, currentImage, labels, filepath.Dir(dockerfileTemplate), flag.Verbose)

// squash if demanded
if flag.Squash {
Expand All @@ -111,31 +114,16 @@ func Run(workdir string, cfg *config.Config, flag config.Flags) error {
// collect tagging commands to keep order
for _, t := range tags {
taggedImg := generateImageName(cfg.Registry, cfg.Prefix, t)
tagger := cmd.New("docker").Arg("tag").
Arg(currentImage).
Arg(taggedImg).
SetVerbose(flag.Verbose).
PreInfo("Tagging " + taggedImg)
taggingTasks = taggingTasks.AddTask(tagger)

pusher := cmd.New("docker").Arg("push").
Arg(taggedImg).
PreInfo("Pushing " + taggedImg)
if !flag.Verbose { // TODO: check it
pusher = pusher.Arg("--quiet")
}
pushTasks = pushTasks.AddTask(pusher)
buildEngine.Tag(currentImage, taggedImg, flag.Verbose)
buildEngine.Push(taggedImg, flag.Verbose)
}

// remove temporary labels
dropTempLabel := cmd.New("docker").Arg("image", "rm", "-f").
Arg(currentImage).
SetVerbose(flag.Verbose)
cleanupTasks = cleanupTasks.AddTask(dropTempLabel)
buildEngine.Remove(currentImage, flag.Verbose)
}

if flag.Build {
err := buildTasks.Run()
err := buildEngine.Run(builder.Build)
util.FailOnError(err, "Building failed with error, check error above. Exiting.")
}

Expand All @@ -146,13 +134,13 @@ func Run(workdir string, cfg *config.Config, flag config.Flags) error {

// continue classical build
if flag.Build {
err := taggingTasks.Run()
err := buildEngine.Run(builder.Tag)
util.FailOnError(err, "Tagging failed with error, check error above. Exiting.")
err = cleanupTasks.Run()
err = buildEngine.Run(builder.Remove)
util.FailOnError(err, "Dropping temporary images failed. Exiting.")
}
if flag.Push {
err := pushTasks.Run()
err := buildEngine.Run(builder.Push)
util.FailOnError(err, "Pushing images failed, check error above. Exiting.")
}

Expand Down

0 comments on commit 1c3dd7c

Please sign in to comment.