-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathbuild.go
More file actions
278 lines (253 loc) · 9.23 KB
/
build.go
File metadata and controls
278 lines (253 loc) · 9.23 KB
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby"
mobybuild "github.com/linuxkit/linuxkit/src/cmd/linuxkit/moby/build"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
const (
defaultNameForStdin = "moby"
defaultSbomFilename = "sbom.spdx.json"
)
type formatList []string
func (f *formatList) String() string {
return fmt.Sprint(*f)
}
func (f *formatList) Set(value string) error {
// allow comma separated options or multiple options
for _, cs := range strings.Split(value, ",") {
*f = append(*f, cs)
}
return nil
}
func (f *formatList) Type() string {
return "[]string"
}
func buildCmd() *cobra.Command {
var (
name string
dir string
outputFile string
sizeString string
pull bool
docker bool
decompressKernel bool
arch string
cacheDir flagOverEnvVarOverDefaultString
buildFormats formatList
outputTypes = mobybuild.OutputTypes()
noSbom bool
sbomOutputFilename string
inputTar string
sbomCurrentTime bool
dryRun bool
)
cmd := &cobra.Command{
Use: "build",
Short: "Build a bootable OS image from a yaml configuration file",
Long: `Build a bootable OS image from a yaml configuration file.
The generated image can be in one of multiple formats which can be run on various platforms.
`,
Example: ` linuxkit build [options] <file>[.yml]`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if name == "" && outputFile == "" {
conf := args[len(args)-1]
if conf == "-" {
name = defaultNameForStdin
} else {
name = strings.TrimSuffix(filepath.Base(conf), filepath.Ext(conf))
}
}
// There are two types of output, they will probably be split into "build" and "package" later
// the basic outputs are tarballs, while the packaged ones are the LinuxKit out formats that
// cannot be streamed but we do allow multiple ones to be built.
if len(buildFormats) == 0 {
if outputFile == "" {
buildFormats = formatList{"kernel+initrd"}
} else {
buildFormats = formatList{"tar"}
}
}
log.Debugf("Formats selected: %s", buildFormats.String())
if len(buildFormats) > 1 {
for _, o := range buildFormats {
if mobybuild.Streamable(o) {
return fmt.Errorf("format type %s must be the only format specified", o)
}
}
}
if len(buildFormats) == 1 && mobybuild.Streamable(buildFormats[0]) {
if outputFile == "" {
outputFile = filepath.Join(dir, name+"."+buildFormats[0])
// stop the errors in the validation below
name = ""
dir = ""
}
} else {
err := mobybuild.ValidateFormats(buildFormats, cacheDir.String())
if err != nil {
return fmt.Errorf("error parsing formats: %v", err)
}
}
if inputTar != "" && pull {
return fmt.Errorf("cannot use --input-tar and --pull together")
}
var outfile *os.File
if outputFile != "" {
if len(buildFormats) > 1 {
return fmt.Errorf("the -output option can only be specified when generating a single output format")
}
if name != "" {
return fmt.Errorf("the -output option cannot be specified with -name")
}
if dir != "" {
return fmt.Errorf("the -output option cannot be specified with -dir")
}
if !mobybuild.Streamable(buildFormats[0]) {
return fmt.Errorf("the -output option cannot be specified for build type %s as it cannot be streamed", buildFormats[0])
}
if outputFile == "-" {
outfile = os.Stdout
} else {
var err error
outfile, err = os.Create(outputFile)
if err != nil {
log.Fatalf("cannot open output file: %v", err)
}
defer func() { _ = outfile.Close() }()
}
}
size, err := getDiskSizeMB(sizeString)
if err != nil {
log.Fatalf("unable to parse disk size: %v", err)
}
var (
m moby.Moby
templatesSupported bool
)
for _, arg := range args {
var config []byte
if conf := arg; conf == "-" {
var err error
config, err = io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("cannot read stdin: %v", err)
}
} else if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
buffer := new(bytes.Buffer)
response, err := http.Get(arg)
if err != nil {
return fmt.Errorf("cannot fetch remote yaml file: %v", err)
}
defer func() { _ = response.Body.Close() }()
_, err = io.Copy(buffer, response.Body)
if err != nil {
return fmt.Errorf("error reading http body: %v", err)
}
config = buffer.Bytes()
} else {
var err error
config, err = os.ReadFile(conf)
if err != nil {
return fmt.Errorf("cannot open config file: %v", err)
}
// templates are only supported for local files
templatesSupported = true
}
var pkgFinder spec.PackageResolver
if templatesSupported {
pkgFinder = createPackageResolver(filepath.Dir(arg))
}
c, err := moby.NewConfig(config, pkgFinder)
if err != nil {
return fmt.Errorf("invalid config: %v", err)
}
m, err = moby.AppendConfig(m, c)
if err != nil {
return fmt.Errorf("cannot append config files: %v", err)
}
}
if dryRun {
yml, err := yaml.Marshal(m)
if err != nil {
return fmt.Errorf("error generating YAML: %v", err)
}
fmt.Println(string(yml))
return nil
}
var (
tf *os.File
w io.Writer
)
if outfile != nil {
w = outfile
} else {
if tf, err = os.CreateTemp("", ""); err != nil {
log.Fatalf("error creating tempfile: %v", err)
}
defer func() { _ = os.Remove(tf.Name()) }()
w = tf
}
if inputTar != "" && inputTar == outputFile {
return fmt.Errorf("input-tar and output file cannot be the same")
}
// this is a weird interface, but currently only streamable types can have additional files
// need to split up the base tarball outputs from the secondary stages
var tp string
if mobybuild.Streamable(buildFormats[0]) {
tp = buildFormats[0]
}
var sbomGenerator *mobybuild.SbomGenerator
if !noSbom {
sbomGenerator, err = mobybuild.NewSbomGenerator(sbomOutputFilename, sbomCurrentTime)
if err != nil {
return fmt.Errorf("error creating sbom generator: %v", err)
}
}
err = mobybuild.Build(m, w, mobybuild.BuildOpts{Pull: pull, BuilderType: tp, DecompressKernel: decompressKernel, CacheDir: cacheDir.String(), DockerCache: docker, Arch: arch, SbomGenerator: sbomGenerator, InputTar: inputTar})
if err != nil {
return fmt.Errorf("%v", err)
}
if outfile == nil {
image := tf.Name()
if err := tf.Close(); err != nil {
return fmt.Errorf("error closing tempfile: %v", err)
}
log.Infof("Create outputs:")
err = mobybuild.Formats(filepath.Join(dir, name), image, buildFormats, size, arch, cacheDir.String())
if err != nil {
return fmt.Errorf("error writing outputs: %v", err)
}
}
return nil
},
}
cmd.Flags().StringVar(&name, "name", "", "Name to use for output files")
cmd.Flags().StringVar(&dir, "dir", "", "Directory for output files, default current directory")
cmd.Flags().StringVar(&outputFile, "o", "", "File to use for a single output, or '-' for stdout")
cmd.Flags().StringVar(&sizeString, "size", "1024M", "Size for output image, if supported and fixed size")
cmd.Flags().BoolVar(&pull, "pull", false, "Always pull images")
cmd.Flags().BoolVar(&docker, "docker", false, "Check for images in docker before linuxkit cache")
cmd.Flags().BoolVar(&decompressKernel, "decompress-kernel", false, "Decompress the Linux kernel (default false)")
cmd.Flags().StringVar(&arch, "arch", runtime.GOARCH, "target architecture for which to build")
cmd.Flags().VarP(&buildFormats, "format", "f", "Formats to create [ "+strings.Join(outputTypes, " ")+" ]")
cmd.Flags().StringVar(&inputTar, "input-tar", "", "path to tar from previous linuxkit build to use as input; if provided, will take files from images from this tar, using OCI images only to replace or update files. Always copies to a temporary working directory to avoid overwriting. Only works if input-tar file has the linuxkit.yaml used to build it in the exact same location. Incompatible with --pull")
cacheDir = flagOverEnvVarOverDefaultString{def: defaultLinuxkitCache(), envVar: envVarCacheDir}
cmd.Flags().Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
cmd.Flags().BoolVar(&noSbom, "no-sbom", false, "suppress consolidation of sboms on input container images to a single sbom and saving in the output filesystem")
cmd.Flags().BoolVar(&sbomCurrentTime, "sbom-current-time", false, "whether to use the current time as the build time in the sbom; this will make the build non-reproducible (default false)")
cmd.Flags().StringVar(&sbomOutputFilename, "sbom-output", defaultSbomFilename, "filename to save the output to in the root filesystem")
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Do not actually build, just print the final yml file that would be used, including all merges and templates")
return cmd
}