main.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package main
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "encoding/hex"
  6. "fmt"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "os/signal"
  11. "strings"
  12. "github.com/spf13/pflag"
  13. )
  14. type Builder struct {
  15. Name string
  16. }
  17. func (b *Builder) Shutdown(ctx context.Context) error {
  18. cmd := exec.CommandContext(
  19. ctx,
  20. "docker",
  21. "buildx",
  22. "rm",
  23. b.Name)
  24. cmd.Stdout = os.Stdout
  25. cmd.Stderr = os.Stderr
  26. return cmd.Run()
  27. }
  28. func StartBuilder(ctx context.Context) (*Builder, error) {
  29. var key [8]byte
  30. if _, err := rand.Read(key[:]); err != nil {
  31. return nil, err
  32. }
  33. name := fmt.Sprintf("go-build-%s", hex.EncodeToString(key[:]))
  34. cmd := exec.CommandContext(
  35. ctx,
  36. "docker",
  37. "buildx",
  38. "create",
  39. "--name",
  40. name)
  41. cmd.Stdout = os.Stdout
  42. cmd.Stderr = os.Stderr
  43. if err := cmd.Run(); err != nil {
  44. return nil, err
  45. }
  46. return &Builder{Name: name}, nil
  47. }
  48. func (b *Builder) Build(
  49. ctx context.Context,
  50. image string,
  51. tags []string,
  52. platforms []string,
  53. buildArgs []string,
  54. ) error {
  55. args := []string{
  56. "buildx",
  57. "build",
  58. "--builder",
  59. b.Name,
  60. "--platform",
  61. strings.Join(platforms, ","),
  62. }
  63. for _, arg := range buildArgs {
  64. args = append(args, "--build-arg", arg)
  65. }
  66. for _, tag := range tags {
  67. args = append(args,
  68. "-t",
  69. fmt.Sprintf("%s:%s", image, tag))
  70. }
  71. args = append(args, "--push", ".")
  72. cmd := exec.CommandContext(
  73. ctx,
  74. "docker",
  75. args...)
  76. cmd.Stdout = os.Stdout
  77. cmd.Stderr = os.Stderr
  78. return cmd.Run()
  79. }
  80. func run(
  81. ctx context.Context,
  82. image string,
  83. tags []string,
  84. platforms []string,
  85. buildArgs []string,
  86. ) error {
  87. builder, err := StartBuilder(ctx)
  88. if err != nil {
  89. return err
  90. }
  91. defer builder.Shutdown(context.Background())
  92. ctx, done := signal.NotifyContext(ctx, os.Interrupt)
  93. defer done()
  94. ch := make(chan error, 1)
  95. go func() {
  96. ch <- builder.Build(ctx, image, tags, platforms, buildArgs)
  97. }()
  98. select {
  99. case <-ctx.Done():
  100. return ctx.Err()
  101. case err := <-ch:
  102. return err
  103. }
  104. }
  105. func main() {
  106. var tags []string
  107. var platforms []string
  108. var buildArgs []string
  109. var image string
  110. pflag.StringSliceVar(
  111. &tags,
  112. "tag",
  113. []string{"latest"},
  114. "tags to apply to the image")
  115. pflag.StringSliceVar(
  116. &platforms,
  117. "platform",
  118. []string{"linux/amd64", "linux/arm64"},
  119. "platforms to build for")
  120. pflag.StringSliceVar(
  121. &buildArgs,
  122. "build-arg",
  123. []string{},
  124. "build arguments to pass to the image")
  125. pflag.StringVar(
  126. &image,
  127. "image",
  128. "kellegous/go",
  129. "the name of the image to build")
  130. pflag.Parse()
  131. if err := run(
  132. context.Background(),
  133. image, tags,
  134. platforms,
  135. buildArgs,
  136. ); err != nil {
  137. log.Panic(err)
  138. }
  139. }