Skip to content

Commit bd7e5cf

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

File tree

10 files changed

+130
-43
lines changed

10 files changed

+130
-43
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-1
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,38 @@ 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+
ImageRef imageTypes.ImageReference
106+
Image string
105107
IsDir bool
106108
IsStdout bool
109+
Path string // Only valid if !IsStdout
110+
Push bool
111+
Type BuildOutputType
112+
}
113+
114+
type BuildOutputType int
115+
116+
const (
117+
_ BuildOutputType = iota
118+
BuildOutputImage
119+
BuildOutputLocal
120+
BuildOutputRegistry
121+
BuildOutputTar
122+
)
123+
124+
// String converts a BuildOutputType into a string.
125+
func (t BuildOutputType) String() string {
126+
switch t {
127+
case BuildOutputImage:
128+
return "image"
129+
case BuildOutputLocal:
130+
return "local"
131+
case BuildOutputRegistry:
132+
return "registry"
133+
case BuildOutputTar:
134+
return "tar"
135+
}
136+
return fmt.Sprintf("unrecognized build output type %d", t)
107137
}
108138

109139
// 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

+10-2
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.ExportFromReader(nil, 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
@@ -2099,7 +2107,7 @@ func (s *StageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOp
20992107
return fmt.Errorf("failed to extract rootfs from given container image: %w", err)
21002108
}
21012109
defer rc.Close()
2102-
err = internalUtil.ExportFromReader(rc, buildOutputOpts)
2110+
err = internalUtil.ExportFromReader(rc, s.executor.store, buildOutputOpts)
21032111
if err != nil {
21042112
return fmt.Errorf("failed to export build output: %w", err)
21052113
}

internal/util/util.go

+19-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"
@@ -60,15 +61,29 @@ func GetTempDir() string {
6061
}
6162

6263
// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout.
63-
func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
64+
func ExportFromReader(input io.ReadCloser, store storage.Store, opts define.BuildOutputOption) error {
6465
var err error
6566
if !filepath.IsAbs(opts.Path) {
6667
opts.Path, err = filepath.Abs(opts.Path)
6768
if err != nil {
6869
return err
6970
}
7071
}
71-
if opts.IsDir {
72+
switch opts.Type {
73+
case define.BuildOutputImage:
74+
if !opts.Push {
75+
return nil
76+
}
77+
78+
libimageOptions := &libimage.PushOptions{}
79+
libimageOptions.Writer = os.Stdout
80+
runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: &types.SystemContext{}})
81+
dest := fmt.Sprintf("%s:%s", opts.ImageRef.Transport().Name(), opts.ImageRef.StringWithinTransport())
82+
_, err = runtime.Push(context.Background(), opts.Image, dest, libimageOptions)
83+
if err != nil {
84+
return fmt.Errorf("failed while pushing image %+q: %w", opts.ImageRef, err)
85+
}
86+
case define.BuildOutputLocal:
7287
// In order to keep this feature as close as possible to
7388
// buildkit it was decided to preserve ownership when
7489
// invoked as root since caller already has access to artifacts
@@ -90,7 +105,7 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
90105
if err != nil {
91106
return fmt.Errorf("failed while performing untar at %q: %w", opts.Path, err)
92107
}
93-
} else {
108+
default:
94109
outFile := os.Stdout
95110
if !opts.IsStdout {
96111
outFile, err = os.Create(opts.Path)
@@ -104,6 +119,7 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
104119
return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err)
105120
}
106121
}
122+
107123
return nil
108124
}
109125

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

+28-17
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,22 @@ 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
583584
return define.BuildOutputOption{Path: "",
584585
IsDir: false,
585586
IsStdout: true}, nil
586587
}
587-
if !strings.Contains(buildOutput, ",") {
588-
// expect default --output <dirname>
589-
return define.BuildOutputOption{Path: buildOutput,
590-
IsDir: true,
591-
IsStdout: false}, nil
588+
589+
out := define.BuildOutputOption{
590+
IsStdout: false,
592591
}
592+
593593
isDir := true
594-
isStdout := false
595594
typeSelected := false
596595
pathSelected := false
597-
path := ""
598596
tokens := strings.Split(buildOutput, ",")
599597
for _, option := range tokens {
600598
arr := strings.SplitN(option, "=", 2)
@@ -607,29 +605,42 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
607605
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
608606
}
609607
typeSelected = true
610-
if arr[1] == "local" {
611-
isDir = true
612-
} else if arr[1] == "tar" {
613-
isDir = false
614-
} else {
608+
switch arr[1] {
609+
case define.BuildOutputLocal.String():
610+
out.IsDir = true
611+
case define.BuildOutputTar.String():
612+
out.IsDir = false
613+
case define.BuildOutputRegistry.String():
614+
// --type=registry ==> --type=image,push=true
615+
out.Type = define.BuildOutputImage
616+
out.Push = true
617+
618+
out.Image = image
619+
imageRef, err := util.ImageStringToImageReference(image)
620+
if err != nil {
621+
return define.BuildOutputOption{}, fmt.Errorf("failed to convert image to ImageReference")
622+
}
623+
out.ImageRef = imageRef
624+
default:
615625
return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", arr[1], buildOutput)
616626
}
617627
case "dest":
618628
if pathSelected {
619629
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
620630
}
621631
pathSelected = true
622-
path = arr[1]
632+
out.Path = arr[1]
623633
default:
624634
return define.BuildOutputOption{}, fmt.Errorf("unrecognized key %q in build output option: %q", arr[0], buildOutput)
625635
}
626636
}
627637

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

632-
if path == "-" {
643+
if out.Path == "-" {
633644
if isDir {
634645
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, type=local and dest=- is not supported", buildOutput)
635646
}
@@ -638,7 +649,7 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
638649
IsStdout: true}, nil
639650
}
640651

641-
return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil
652+
return out, nil
642653
}
643654

644655
// 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)