/
kubectl_buildkit.go
147 lines (118 loc) · 4.45 KB
/
kubectl_buildkit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2024 The Carvel Authors.
// SPDX-License-Identifier: Apache-2.0
package image
import (
"bytes"
"fmt"
"io"
"os/exec"
"regexp"
ctlb "carvel.dev/kbld/pkg/kbld/builder"
ctlconf "carvel.dev/kbld/pkg/kbld/config"
ctllog "carvel.dev/kbld/pkg/kbld/logger"
regname "github.com/google/go-containerregistry/pkg/name"
)
var (
// Example output that includes final digest:
// ...
// #10 exporting layers done
// #10 exporting manifest sha256:55d863c4231ec285b88516942fd5d636216c36b6a686a20bf28d1aa5125c16b7 0.0s done
// #10 exporting config sha256:aa1fce99c57c864cc8d98f7ff4a54bc3b9b7e3f63a741de926a3393bc76f5986 0.0s done
// ...
// #10 exporting layers done
// #10 exporting manifest sha256:55d863c4231ec285b88516942fd5d636216c36b6a686a20bf28d1aa5125c16b7 done
// #10 exporting config sha256:aa1fce99c57c864cc8d98f7ff4a54bc3b9b7e3f63a741de926a3393bc76f5986 done
// #10 pushing layers
kubectlBuildkitDigest = regexp.MustCompile("exporting manifest (sha256:)?([0-9a-z]+) ")
)
type KubectlBuildkit struct {
logger ctllog.Logger
}
func NewKubectlBuildkit(logger ctllog.Logger) KubectlBuildkit {
return KubectlBuildkit{logger}
}
func (d KubectlBuildkit) BuildAndPush(image, directory string,
imgDst *ctlconf.ImageDestination, opts ctlconf.SourceKubectlBuildkitOpts) (string, error) {
tagRef, err := d.tagRef(image, imgDst)
if err != nil {
return "", err
}
prefixedLogger := d.logger.NewPrefixedWriter(image + " | ")
prefixedLogger.Write([]byte(fmt.Sprintf("starting build (using kubectl buildkit): %s -> %s\n", directory, tagRef)))
defer prefixedLogger.Write([]byte("finished build (using kubectl buildkit)\n"))
var stdoutBuf, stderrBuf bytes.Buffer
cmdArgs := []string{"buildkit", "build", "--progress=plain"}
if opts.Build.Target != nil {
cmdArgs = append(cmdArgs, "--target", *opts.Build.Target)
}
if opts.Build.Platform != nil {
cmdArgs = append(cmdArgs, "--platform", *opts.Build.Platform)
}
if opts.Build.Pull != nil && *opts.Build.Pull {
cmdArgs = append(cmdArgs, "--pull")
}
if opts.Build.NoCache != nil && *opts.Build.NoCache {
cmdArgs = append(cmdArgs, "--no-cache")
}
if opts.Build.File != nil {
// Since docker command is executed with cwd of directory,
// Dockerfile path doesnt need to be joined with it
cmdArgs = append(cmdArgs, "--file", *opts.Build.File)
}
if opts.Build.RawOptions != nil {
cmdArgs = append(cmdArgs, *opts.Build.RawOptions...)
}
if imgDst != nil {
cmdArgs = append(cmdArgs, "--push")
// https://github.com/vmware-tanzu/buildkit-cli-for-kubectl/blob/main/docs/multiarch.md#using-a-registry
// > it's possible to skip specifying the --registry-secret flag to kubectl build by naming the secret the same name as the builder
}
cmdArgs = append(cmdArgs, "--tag", tagRef, ".")
cmd := exec.Command("kubectl", cmdArgs...)
cmd.Dir = directory
cmd.Stdout = io.MultiWriter(&stdoutBuf, prefixedLogger)
cmd.Stderr = io.MultiWriter(&stderrBuf, prefixedLogger)
err = cmd.Run()
if err != nil {
prefixedLogger.Write([]byte(fmt.Sprintf("error: %s\n", err)))
return "", err
}
// Exercise digest finding logic regardless of pushing or not
digestMatches := kubectlBuildkitDigest.FindStringSubmatch(stderrBuf.String())
if len(digestMatches) != 3 {
return "", fmt.Errorf("Expected to find image digest in build output but did not")
}
// Since kubectl builtkit project targets both containerd and Docker daemon
// and Docker daemon does not support use of digests for locally loaded images
// we can only return digest ref when image is pushed to registry
if imgDst != nil {
digestRefStr := imgDst.NewImage + "@sha256:" + digestMatches[2]
digestRef, err := regname.NewDigest(digestRefStr, regname.WeakValidation)
if err != nil {
return "", fmt.Errorf("Validating destination digest ref '%s': %s", digestRefStr, err)
}
return digestRef.Name(), nil
}
return tagRef, nil
}
func (d KubectlBuildkit) tagRef(image string, imgDst *ctlconf.ImageDestination) (string, error) {
tb := ctlb.TagBuilder{}
randPrefix50, err := tb.RandomStr50()
if err != nil {
return "", fmt.Errorf("Generating tmp image suffix: %s", err)
}
tag := tb.CheckTagLen128(fmt.Sprintf(
"%s-%s",
randPrefix50,
tb.TrimStr(tb.CleanStr(image), 50),
))
if imgDst != nil {
tagRef := imgDst.NewImage + ":" + tag
_, err := regname.NewTag(tagRef, regname.WeakValidation)
if err != nil {
return "", fmt.Errorf("Validating destination tag ref '%s': %s", tagRef, err)
}
return tagRef, nil
}
return "kbld:" + tag, nil
}