Skip to content

Commit

Permalink
build: add support for --push
Browse files Browse the repository at this point in the history
Signed-off-by: danishprakash <danish.prakash@suse.com>
  • Loading branch information
danishprakash committed Mar 23, 2023
1 parent a8ba52d commit f934001
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 33 deletions.
1 change: 1 addition & 0 deletions define/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ type BuildOptions struct {
// It allows end user to export recently built rootfs into a directory or tar.
// See the documentation of 'buildah build --output' for the details of the format.
BuildOutput string
Push bool
// Additional tags to add to the image that we write, if we know of a
// way to add them.
AdditionalTags []string
Expand Down
2 changes: 2 additions & 0 deletions define/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type Secret struct {

// BuildOutputOptions contains the the outcome of parsing the value of a build --output flag
type BuildOutputOption struct {
Type string
Attrs map[string]string
Path string // Only valid if !IsStdout
IsDir bool
IsStdout bool
Expand Down
2 changes: 2 additions & 0 deletions imagebuildah/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Executor struct {
registry string
ignoreUnrecognizedInstructions bool
quiet bool
push bool
runtime string
runtimeArgs []string
transientMounts []Mount
Expand Down Expand Up @@ -237,6 +238,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
registry: options.Registry,
ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions,
quiet: options.Quiet,
push: options.Push, // TODO: not needed if planning to update buildOutput in cli/build
runtime: options.Runtime,
runtimeArgs: options.RuntimeArgs,
transientMounts: transientMounts,
Expand Down
12 changes: 10 additions & 2 deletions imagebuildah/stage_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
canGenerateBuildOutput := (s.executor.buildOutput != "" && lastStage)
if canGenerateBuildOutput {
logrus.Debugf("Generating custom build output with options %q", s.executor.buildOutput)
buildOutputOption, err = parse.GetBuildOutput(s.executor.buildOutput)
buildOutputOption, err = parse.GetBuildOutput(s.executor.buildOutput, s.executor.output)
if err != nil {
return "", nil, fmt.Errorf("failed to parse build output: %w", err)
}
Expand Down Expand Up @@ -2080,6 +2080,14 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer
}

func (s *StageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOption) error {
if buildOutputOpts.Type == "image" {
err := internalUtil.ExportFromReader(nil, s.executor.store, buildOutputOpts)
if err != nil {
return fmt.Errorf("failed to export build output: %w", err)
}
return nil
}

extractRootfsOpts := buildah.ExtractRootfsOptions{}
if unshare.IsRootless() {
// In order to maintain as much parity as possible
Expand All @@ -2099,7 +2107,7 @@ func (s *StageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOp
return fmt.Errorf("failed to extract rootfs from given container image: %w", err)
}
defer rc.Close()
err = internalUtil.ExportFromReader(rc, buildOutputOpts)
err = internalUtil.ExportFromReader(rc, s.executor.store, buildOutputOpts)
if err != nil {
return fmt.Errorf("failed to export build output: %w", err)
}
Expand Down
68 changes: 55 additions & 13 deletions internal/util/util.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package util

import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/containers/buildah/define"
"github.com/containers/common/libimage"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
encconfig "github.com/containers/ocicrypt/config"
enchelpers "github.com/containers/ocicrypt/helpers"
Expand All @@ -16,6 +20,7 @@ import (
"github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/unshare"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)

// LookupImage returns *Image to corresponding imagename or id
Expand Down Expand Up @@ -60,15 +65,51 @@ func GetTempDir() string {
}

// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout.
func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
func ExportFromReader(input io.ReadCloser, store storage.Store, opts define.BuildOutputOption) error {
var err error
if !filepath.IsAbs(opts.Path) {
opts.Path, err = filepath.Abs(opts.Path)
if err != nil {
return err
}
}
if opts.IsDir {
if opts.Type == "image" {
if opts.Attrs["push"] != "true" {
return nil
}

image := opts.Attrs["name"]
destSpec := opts.Attrs["name"]
dest, err := alltransports.ParseImageName(destSpec)
// add the docker:// transport to see if they neglected it.
if err != nil {
destTransport := strings.Split(destSpec, ":")[0]
if t := transports.Get(destTransport); t != nil {
return err
}

if strings.Contains(destSpec, "://") {
return err
}

destSpec = "docker://" + destSpec
dest2, err2 := alltransports.ParseImageName(destSpec)
if err2 != nil {
return err
}
dest = dest2
logrus.Debugf("Assuming docker:// as the transport method for DESTINATION: %s", destSpec)
}

libimageOptions := &libimage.PushOptions{}
libimageOptions.Writer = os.Stdout
runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: &types.SystemContext{}})
destString := fmt.Sprintf("%s:%s", dest.Transport().Name(), dest.StringWithinTransport())
_, err = runtime.Push(context.Background(), image, destString, libimageOptions)
if err != nil {
return fmt.Errorf("failed while pushing image %+q: %w", dest, err)
}
} else if opts.IsDir {
// In order to keep this feature as close as possible to
// buildkit it was decided to preserve ownership when
// invoked as root since caller already has access to artifacts
Expand All @@ -89,21 +130,22 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
err = chrootarchive.Untar(input, opts.Path, &archive.TarOptions{NoLchown: noLChown})
if err != nil {
return fmt.Errorf("failed while performing untar at %q: %w", opts.Path, err)
}
} else {
outFile := os.Stdout
if !opts.IsStdout {
outFile, err = os.Create(opts.Path)
} else {
outFile := os.Stdout
if !opts.IsStdout {
outFile, err = os.Create(opts.Path)
if err != nil {
return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err)
}
defer outFile.Close()
}
_, err = io.Copy(outFile, input)
if err != nil {
return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err)
return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err)
}
defer outFile.Close()
}
_, err = io.Copy(outFile, input)
if err != nil {
return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err)
}
}

return nil
}

Expand Down
8 changes: 7 additions & 1 deletion pkg/cli/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,19 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
timestamp = &t
}
if c.Flag("output").Changed {
buildOption, err := parse.GetBuildOutput(iopts.BuildOutput)
buildOption, err := parse.GetBuildOutput(iopts.BuildOutput, output)
if err != nil {
return options, nil, nil, err
}
if buildOption.IsStdout {
iopts.Quiet = true
}
}
if c.Flag("push").Changed {
if len(iopts.BuildOutput) == 0 {
iopts.BuildOutput = "type=registry"
}
}
var cacheTo []reference.Named
var cacheFrom []reference.Named
cacheTo = nil
Expand Down Expand Up @@ -406,6 +411,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
Platforms: platforms,
PullPolicy: pullPolicy,
PullPushRetryDelay: pullPushRetryDelay,
Push: iopts.Push,
Quiet: iopts.Quiet,
RemoveIntermediateCtrs: iopts.Rm,
ReportWriter: reporter,
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type BudResults struct {
Pull string
PullAlways bool
PullNever bool
Push bool
Quiet bool
IdentityLabel bool
Rm bool
Expand Down Expand Up @@ -270,6 +271,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
fs.BoolVar(&flags.Stdin, "stdin", false, "pass stdin into containers")
fs.StringArrayVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image")
fs.StringVarP(&flags.BuildOutput, "output", "o", "", "output destination (format: type=local,dest=path)")
fs.BoolVar(&flags.Push, "push", false, "Shorthand for `--output=type=registry`")
fs.StringVar(&flags.Target, "target", "", "set the target build stage to build")
fs.Int64Var(&flags.Timestamp, "timestamp", 0, "set created timestamp to the specified epoch seconds to allow for deterministic builds, defaults to current time")
fs.BoolVar(&flags.TLSVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
Expand Down
47 changes: 30 additions & 17 deletions pkg/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,25 +576,30 @@ func AuthConfig(creds string) (*types.DockerAuthConfig, error) {

// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag.
// Takes `buildOutput` as string and returns BuildOutputOption
func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
func GetBuildOutput(buildOutput, image string) (define.BuildOutputOption, error) {
if len(buildOutput) == 1 && buildOutput == "-" {
// Feature parity with buildkit, output tar to stdout
// Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
return define.BuildOutputOption{Path: "",
IsDir: false,
IsStdout: true}, nil
}
if !strings.Contains(buildOutput, ",") {
// expect default --output <dirname>
return define.BuildOutputOption{Path: buildOutput,
IsDir: true,
IsStdout: false}, nil

// if !strings.Contains(buildOutput, ",") {
// // expect default --output <dirname>
// return define.BuildOutputOption{Path: buildOutput,
// IsDir: true,
// IsStdout: false}, nil
// }

out := define.BuildOutputOption{
Attrs: map[string]string{},
IsStdout: false,
}

isDir := true
isStdout := false
typeSelected := false
pathSelected := false
path := ""
tokens := strings.Split(buildOutput, ",")
for _, option := range tokens {
arr := strings.SplitN(option, "=", 2)
Expand All @@ -607,29 +612,36 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
}
typeSelected = true
if arr[1] == "local" {
isDir = true
} else if arr[1] == "tar" {
isDir = false
} else {
switch arr[1] {
case "local":
out.IsDir = true
case "tar":
out.IsDir = false
case "registry":
out.IsDir = true
out.Type = "image"
out.Attrs["push"] = "true"
out.Attrs["name"] = image
default:
return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", arr[1], buildOutput)
}
case "dest":
if pathSelected {
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
}
pathSelected = true
path = arr[1]
out.Path = arr[1]
default:
return define.BuildOutputOption{}, fmt.Errorf("unrecognized key %q in build output option: %q", arr[0], buildOutput)
}
}

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

if path == "-" {
if out.Path == "-" {
if isDir {
return define.BuildOutputOption{}, fmt.Errorf("invalid build output option %q, type=local and dest=- is not supported", buildOutput)
}
Expand All @@ -638,7 +650,8 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
IsStdout: true}, nil
}

return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil
return out, nil
// return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil
}

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

0 comments on commit f934001

Please sign in to comment.