Skip to content

Commit 4b19451

Browse files
committed
build: add support for --push
Signed-off-by: danishprakash <[email protected]>
1 parent a8ba52d commit 4b19451

File tree

10 files changed

+144
-49
lines changed

10 files changed

+144
-49
lines changed

cmd/buildah/push.go

+2-19
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/containers/image/v5/manifest"
1919
"github.com/containers/image/v5/pkg/compression"
2020
"github.com/containers/image/v5/transports"
21-
"github.com/containers/image/v5/transports/alltransports"
2221
"github.com/containers/storage"
2322
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
2423
"github.com/sirupsen/logrus"
@@ -147,25 +146,9 @@ func pushCmd(c *cobra.Command, args []string, iopts pushOptions) error {
147146
return err
148147
}
149148

150-
dest, err := alltransports.ParseImageName(destSpec)
151-
// add the docker:// transport to see if they neglected it.
149+
dest, err := util.ImageStringToImageReference(destSpec)
152150
if err != nil {
153-
destTransport := strings.Split(destSpec, ":")[0]
154-
if t := transports.Get(destTransport); t != nil {
155-
return err
156-
}
157-
158-
if strings.Contains(destSpec, "://") {
159-
return err
160-
}
161-
162-
destSpec = "docker://" + destSpec
163-
dest2, err2 := alltransports.ParseImageName(destSpec)
164-
if err2 != nil {
165-
return err
166-
}
167-
dest = dest2
168-
logrus.Debugf("Assuming docker:// as the transport method for DESTINATION: %s", destSpec)
151+
return fmt.Errorf("generating image reference: %w", err)
169152
}
170153

171154
systemContext, err := parse.SystemContextFromOptions(c)

define/build.go

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ type BuildOptions struct {
163163
// It allows end user to export recently built rootfs into a directory or tar.
164164
// See the documentation of 'buildah build --output' for the details of the format.
165165
BuildOutput string
166+
Push bool
166167
// Additional tags to add to the image that we write, if we know of a
167168
// way to add them.
168169
AdditionalTags []string

define/types.go

+31-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616

1717
"github.com/containers/image/v5/manifest"
18+
imageTypes "github.com/containers/image/v5/types"
1819
"github.com/containers/storage/pkg/archive"
1920
"github.com/containers/storage/pkg/chrootarchive"
2021
"github.com/containers/storage/pkg/ioutils"
@@ -101,9 +102,37 @@ type Secret struct {
101102

102103
// BuildOutputOptions contains the the outcome of parsing the value of a build --output flag
103104
type BuildOutputOption struct {
104-
Path string // Only valid if !IsStdout
105-
IsDir bool
105+
ImageRef imageTypes.ImageReference
106+
Image string
106107
IsStdout bool
108+
Path string // Only valid if !IsStdout
109+
Push bool
110+
Type BuildOutputType
111+
}
112+
113+
type BuildOutputType int
114+
115+
const (
116+
_ BuildOutputType = iota
117+
BuildOutputImage
118+
BuildOutputLocal
119+
BuildOutputRegistry
120+
BuildOutputTar
121+
)
122+
123+
// String converts a BuildOutputType into a string.
124+
func (t BuildOutputType) String() string {
125+
switch t {
126+
case BuildOutputImage:
127+
return "image"
128+
case BuildOutputLocal:
129+
return "local"
130+
case BuildOutputRegistry:
131+
return "registry"
132+
case BuildOutputTar:
133+
return "tar"
134+
}
135+
return fmt.Sprintf("unrecognized build output type %d", t)
107136
}
108137

109138
// TempDirForURL checks if the passed-in string looks like a URL or -. If it is,

imagebuildah/executor.go

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type Executor struct {
7373
registry string
7474
ignoreUnrecognizedInstructions bool
7575
quiet bool
76+
push bool
7677
runtime string
7778
runtimeArgs []string
7879
transientMounts []Mount
@@ -237,6 +238,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
237238
registry: options.Registry,
238239
ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions,
239240
quiet: options.Quiet,
241+
push: options.Push, // TODO: not needed if planning to update buildOutput in cli/build
240242
runtime: options.Runtime,
241243
runtimeArgs: options.RuntimeArgs,
242244
transientMounts: transientMounts,

imagebuildah/stage_executor.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
10191019
canGenerateBuildOutput := (s.executor.buildOutput != "" && lastStage)
10201020
if canGenerateBuildOutput {
10211021
logrus.Debugf("Generating custom build output with options %q", s.executor.buildOutput)
1022-
buildOutputOption, err = parse.GetBuildOutput(s.executor.buildOutput)
1022+
buildOutputOption, err = parse.GetBuildOutput(s.executor.buildOutput, s.executor.output)
10231023
if err != nil {
10241024
return "", nil, fmt.Errorf("failed to parse build output: %w", err)
10251025
}
@@ -2080,6 +2080,14 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer
20802080
}
20812081

20822082
func (s *StageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOption) error {
2083+
if buildOutputOpts.Type == define.BuildOutputImage {
2084+
err := internalUtil.ExportImage(s.executor.store, buildOutputOpts)
2085+
if err != nil {
2086+
return fmt.Errorf("failed to export build output: %w", err)
2087+
}
2088+
return nil
2089+
}
2090+
20832091
extractRootfsOpts := buildah.ExtractRootfsOptions{}
20842092
if unshare.IsRootless() {
20852093
// In order to maintain as much parity as possible

internal/util/util.go

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package util
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"os"
@@ -59,16 +60,35 @@ func GetTempDir() string {
5960
return "/var/tmp"
6061
}
6162

63+
// ExportImage copies contents of the image to a new location
64+
func ExportImage(store storage.Store, opts define.BuildOutputOption) error {
65+
if !opts.Push {
66+
return nil
67+
}
68+
69+
libimageOptions := &libimage.PushOptions{}
70+
libimageOptions.Writer = os.Stdout
71+
runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: &types.SystemContext{}})
72+
dest := fmt.Sprintf("%s:%s", opts.ImageRef.Transport().Name(), opts.ImageRef.StringWithinTransport())
73+
_, err = runtime.Push(context.Background(), opts.Image, dest, libimageOptions)
74+
if err != nil {
75+
return fmt.Errorf("failed while pushing image %+q: %w", opts.ImageRef, err)
76+
}
77+
78+
return nil
79+
}
80+
6281
// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout.
63-
func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
82+
func ExportFromReader(input io.ReadCloser, opts define.BuildOutputOption) error {
6483
var err error
6584
if !filepath.IsAbs(opts.Path) {
6685
opts.Path, err = filepath.Abs(opts.Path)
6786
if err != nil {
6887
return err
6988
}
7089
}
71-
if opts.IsDir {
90+
switch opts.Type {
91+
case define.BuildOutputLocal:
7292
// In order to keep this feature as close as possible to
7393
// buildkit it was decided to preserve ownership when
7494
// invoked as root since caller already has access to artifacts
@@ -90,7 +110,7 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
90110
if err != nil {
91111
return fmt.Errorf("failed while performing untar at %q: %w", opts.Path, err)
92112
}
93-
} else {
113+
case define.BuildOutputTar:
94114
outFile := os.Stdout
95115
if !opts.IsStdout {
96116
outFile, err = os.Create(opts.Path)
@@ -103,7 +123,10 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
103123
if err != nil {
104124
return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err)
105125
}
126+
default:
127+
return fmt.Errorf("build output type %s not supported", opts.Type)
106128
}
129+
107130
return nil
108131
}
109132

pkg/cli/build.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,21 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
292292
timestamp = &t
293293
}
294294
if c.Flag("output").Changed {
295-
buildOption, err := parse.GetBuildOutput(iopts.BuildOutput)
295+
buildOption, err := parse.GetBuildOutput(iopts.BuildOutput, output)
296296
if err != nil {
297297
return options, nil, nil, err
298298
}
299299
if buildOption.IsStdout {
300300
iopts.Quiet = true
301301
}
302302
}
303+
if c.Flag("push").Changed {
304+
if len(iopts.BuildOutput) == 0 {
305+
iopts.BuildOutput = "type=registry"
306+
} else {
307+
return options, nil, nil, fmt.Errorf("cannot set both --push and --output")
308+
}
309+
}
303310
var cacheTo []reference.Named
304311
var cacheFrom []reference.Named
305312
cacheTo = nil
@@ -406,6 +413,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
406413
Platforms: platforms,
407414
PullPolicy: pullPolicy,
408415
PullPushRetryDelay: pullPushRetryDelay,
416+
Push: iopts.Push,
409417
Quiet: iopts.Quiet,
410418
RemoveIntermediateCtrs: iopts.Rm,
411419
ReportWriter: reporter,

pkg/cli/common.go

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type BudResults struct {
8080
Pull string
8181
PullAlways bool
8282
PullNever bool
83+
Push bool
8384
Quiet bool
8485
IdentityLabel bool
8586
Rm bool
@@ -270,6 +271,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
270271
fs.BoolVar(&flags.Stdin, "stdin", false, "pass stdin into containers")
271272
fs.StringArrayVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image")
272273
fs.StringVarP(&flags.BuildOutput, "output", "o", "", "output destination (format: type=local,dest=path)")
274+
fs.BoolVar(&flags.Push, "push", false, "Shorthand for `--output=type=registry`")
273275
fs.StringVar(&flags.Target, "target", "", "set the target build stage to build")
274276
fs.Int64Var(&flags.Timestamp, "timestamp", 0, "set created timestamp to the specified epoch seconds to allow for deterministic builds, defaults to current time")
275277
fs.BoolVar(&flags.TLSVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")

pkg/parse/parse.go

+36-23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/containers/buildah/define"
1919
internalParse "github.com/containers/buildah/internal/parse"
2020
"github.com/containers/buildah/pkg/sshagent"
21+
"github.com/containers/buildah/util"
2122
"github.com/containers/common/pkg/config"
2223
"github.com/containers/common/pkg/parse"
2324
"github.com/containers/image/v5/docker/reference"
@@ -576,25 +577,23 @@ func AuthConfig(creds string) (*types.DockerAuthConfig, error) {
576577

577578
// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag.
578579
// Takes `buildOutput` as string and returns BuildOutputOption
579-
func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
580+
func GetBuildOutput(buildOutput, image string) (define.BuildOutputOption, error) {
580581
if len(buildOutput) == 1 && buildOutput == "-" {
581582
// Feature parity with buildkit, output tar to stdout
582583
// Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
583-
return define.BuildOutputOption{Path: "",
584-
IsDir: false,
585-
IsStdout: true}, nil
584+
return define.BuildOutputOption{
585+
Path: "",
586+
IsStdout: true,
587+
}, nil
586588
}
587-
if !strings.Contains(buildOutput, ",") {
588-
// expect default --output <dirname>
589-
return define.BuildOutputOption{Path: buildOutput,
590-
IsDir: true,
591-
IsStdout: false}, nil
589+
590+
out := define.BuildOutputOption{
591+
IsStdout: false,
592592
}
593+
593594
isDir := true
594-
isStdout := false
595595
typeSelected := false
596596
pathSelected := false
597-
path := ""
598597
tokens := strings.Split(buildOutput, ",")
599598
for _, option := range tokens {
600599
arr := strings.SplitN(option, "=", 2)
@@ -607,38 +606,52 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
607606
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
608607
}
609608
typeSelected = true
610-
if arr[1] == "local" {
611-
isDir = true
612-
} else if arr[1] == "tar" {
613-
isDir = false
614-
} else {
609+
switch arr[1] {
610+
case "local":
611+
out.Type = define.BuildOutputLocal
612+
case "tar":
613+
out.Type = define.BuildOutputTar
614+
case "registry":
615+
// --type=registry ==> --type=image,push=true
616+
out.Type = define.BuildOutputImage
617+
out.Push = true
618+
619+
out.Image = image
620+
imageRef, err := util.ImageStringToImageReference(image)
621+
if err != nil {
622+
return define.BuildOutputOption{}, fmt.Errorf("failed to convert image to ImageReference")
623+
}
624+
out.ImageRef = imageRef
625+
default:
615626
return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", arr[1], buildOutput)
616627
}
617628
case "dest":
618629
if pathSelected {
619630
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
620631
}
621632
pathSelected = true
622-
path = arr[1]
633+
out.Path = arr[1]
623634
default:
624635
return define.BuildOutputOption{}, fmt.Errorf("unrecognized key %q in build output option: %q", arr[0], buildOutput)
625636
}
626637
}
627638

628-
if !typeSelected || !pathSelected {
639+
if !typeSelected && !pathSelected {
640+
// TODO: update error message
629641
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, accepted keys are type and dest must be present", buildOutput)
630642
}
631643

632-
if path == "-" {
644+
if out.Path == "-" {
633645
if isDir {
634646
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, type=local and dest=- is not supported", buildOutput)
635647
}
636-
return define.BuildOutputOption{Path: "",
637-
IsDir: false,
638-
IsStdout: true}, nil
648+
return define.BuildOutputOption{
649+
Path: "",
650+
IsStdout: true,
651+
}, nil
639652
}
640653

641-
return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil
654+
return out, nil
642655
}
643656

644657
// IDMappingOptions parses the build options related to user namespaces and ID mapping.

util/util.go

+26
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/containers/image/v5/docker/reference"
2020
"github.com/containers/image/v5/pkg/shortnames"
2121
"github.com/containers/image/v5/signature"
22+
"github.com/containers/image/v5/transports"
2223
"github.com/containers/image/v5/transports/alltransports"
2324
"github.com/containers/image/v5/types"
2425
"github.com/containers/storage"
@@ -50,6 +51,31 @@ func StringInSlice(s string, slice []string) bool {
5051
return util.StringInSlice(s, slice)
5152
}
5253

54+
func ImageStringToImageReference(image string) (types.ImageReference, error) {
55+
dest, err := alltransports.ParseImageName(image)
56+
// add the docker:// transport to see if they neglected it.
57+
if err != nil {
58+
destTransport := strings.Split(image, ":")[0]
59+
if t := transports.Get(destTransport); t != nil {
60+
return nil, err
61+
}
62+
63+
if strings.Contains(image, "://") {
64+
return nil, err
65+
}
66+
67+
image = "docker://" + image
68+
dest2, err2 := alltransports.ParseImageName(image)
69+
if err2 != nil {
70+
return nil, err
71+
}
72+
dest = dest2
73+
logrus.Debugf("Assuming docker:// as the transport method for DESTINATION: %s", image)
74+
}
75+
76+
return dest, nil
77+
}
78+
5379
// resolveName checks if name is a valid image name, and if that name doesn't
5480
// include a domain portion, returns a list of the names which it might
5581
// correspond to in the set of configured registries, and the transport used to

0 commit comments

Comments
 (0)