Files
podman/cmd/podman/build.go
Brent Baude 9ca4b6c6f5 add os|arch attributes when building
when building images, we can now add the os and arch of the image using overrides from the commandline.  the commandline options set sane defaults so we use those as well.

Fixes: #5503

Signed-off-by: Brent Baude <bbaude@redhat.com>
2020-03-15 12:49:42 -05:00

393 lines
12 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/adapter"
"github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
buildCommand cliconfig.BuildValues
buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory."
layerValues buildahcli.LayerResults
budFlagsValues buildahcli.BudResults
fromAndBudValues buildahcli.FromAndBudResults
userNSValues buildahcli.UserNSResults
namespaceValues buildahcli.NameSpaceResults
podBuildValues cliconfig.PodmanBuildResults
_buildCommand = &cobra.Command{
Use: "build [flags] CONTEXT",
Short: "Build an image using instructions from Containerfiles",
Long: buildDescription,
RunE: func(cmd *cobra.Command, args []string) error {
buildCommand.InputArgs = args
buildCommand.GlobalFlags = MainGlobalOpts
buildCommand.BudResults = &budFlagsValues
buildCommand.UserNSResults = &userNSValues
buildCommand.FromAndBudResults = &fromAndBudValues
buildCommand.LayerResults = &layerValues
buildCommand.NameSpaceResults = &namespaceValues
buildCommand.PodmanBuildResults = &podBuildValues
buildCommand.Remote = remoteclient
return buildCmd(&buildCommand)
},
Example: `podman build .
podman build --creds=username:password -t imageName -f Containerfile.simple .
podman build --layers --force-rm --tag imageName .`,
}
)
func initBuild() {
buildCommand.Command = _buildCommand
buildCommand.SetHelpTemplate(HelpTemplate())
buildCommand.SetUsageTemplate(UsageTemplate())
flags := buildCommand.Flags()
flags.SetInterspersed(true)
budFlags := buildahcli.GetBudFlags(&budFlagsValues)
flag := budFlags.Lookup("pull")
if err := flag.Value.Set("true"); err != nil {
logrus.Error("unable to set pull flag to true")
}
flag.DefValue = "true"
layerFlags := buildahcli.GetLayerFlags(&layerValues)
flag = layerFlags.Lookup("layers")
if err := flag.Value.Set(useLayers()); err != nil {
logrus.Error("unable to set uselayers")
}
flag.DefValue = useLayers()
flag = layerFlags.Lookup("force-rm")
if err := flag.Value.Set("true"); err != nil {
logrus.Error("unable to set force-rm flag to true")
}
flag.DefValue = "true"
podmanBuildFlags := GetPodmanBuildFlags(&podBuildValues)
flag = podmanBuildFlags.Lookup("squash-all")
if err := flag.Value.Set("false"); err != nil {
logrus.Error("unable to set squash-all flag to false")
}
flag.DefValue = "true"
fromAndBugFlags, err := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues)
if err != nil {
logrus.Errorf("failed to setup podman build flags: %v", err)
}
flags.AddFlagSet(&budFlags)
flags.AddFlagSet(&fromAndBugFlags)
flags.AddFlagSet(&layerFlags)
flags.AddFlagSet(&podmanBuildFlags)
markFlagHidden(flags, "signature-policy")
}
// GetPodmanBuildFlags flags used only by `podman build` and not by
// `buildah bud`.
func GetPodmanBuildFlags(flags *cliconfig.PodmanBuildResults) pflag.FlagSet {
fs := pflag.FlagSet{}
fs.BoolVar(&flags.SquashAll, "squash-all", false, "Squash all layers into a single layer.")
return fs
}
func getContainerfiles(files []string) []string {
var containerfiles []string
for _, f := range files {
if f == "-" {
containerfiles = append(containerfiles, "/dev/stdin")
} else {
containerfiles = append(containerfiles, f)
}
}
return containerfiles
}
func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) {
var ret []buildah.NamespaceOption
if c.Network != "" {
switch {
case c.Network == "host":
ret = append(ret, buildah.NamespaceOption{
Name: string(specs.NetworkNamespace),
Host: true,
})
case c.Network == "container":
ret = append(ret, buildah.NamespaceOption{
Name: string(specs.NetworkNamespace),
})
case c.Network[0] == '/':
ret = append(ret, buildah.NamespaceOption{
Name: string(specs.NetworkNamespace),
Path: c.Network,
})
default:
return nil, fmt.Errorf("unsupported configuration network=%s", c.Network)
}
}
return ret, nil
}
func buildCmd(c *cliconfig.BuildValues) error {
if (c.Flags().Changed("squash") && c.Flags().Changed("layers")) ||
(c.Flags().Changed("squash-all") && c.Flags().Changed("layers")) ||
(c.Flags().Changed("squash-all") && c.Flags().Changed("squash")) {
return fmt.Errorf("cannot specify squash, squash-all and layers options together")
}
// The following was taken directly from containers/buildah/cmd/bud.go
// TODO Find a away to vendor more of this in rather than copy from bud
output := ""
tags := []string{}
if c.Flag("tag").Changed {
tags = c.Tag
if len(tags) > 0 {
output = tags[0]
tags = tags[1:]
}
}
if c.BudResults.Authfile != "" {
if _, err := os.Stat(c.BudResults.Authfile); err != nil {
return errors.Wrapf(err, "error getting authfile %s", c.BudResults.Authfile)
}
}
pullPolicy := imagebuildah.PullNever
if c.Pull {
pullPolicy = imagebuildah.PullIfMissing
}
if c.PullAlways {
pullPolicy = imagebuildah.PullAlways
}
args := make(map[string]string)
if c.Flag("build-arg").Changed {
for _, arg := range c.BuildArg {
av := strings.SplitN(arg, "=", 2)
if len(av) > 1 {
args[av[0]] = av[1]
} else {
delete(args, av[0])
}
}
}
containerfiles := getContainerfiles(c.File)
format, err := getFormat(&c.PodmanCommand)
if err != nil {
return nil
}
contextDir := ""
cliArgs := c.InputArgs
layers := c.Layers // layers for podman defaults to true
// Check to see if the BUILDAH_LAYERS environment variable is set and override command-line
if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok {
layers = buildahcli.UseLayers()
}
if len(cliArgs) > 0 {
// The context directory could be a URL. Try to handle that.
tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", cliArgs[0])
if err != nil {
return errors.Wrapf(err, "error prepping temporary context directory")
}
if tempDir != "" {
// We had to download it to a temporary directory.
// Delete it later.
defer func() {
if err = os.RemoveAll(tempDir); err != nil {
logrus.Errorf("error removing temporary directory %q: %v", contextDir, err)
}
}()
contextDir = filepath.Join(tempDir, subDir)
} else {
// Nope, it was local. Use it as is.
absDir, err := filepath.Abs(cliArgs[0])
if err != nil {
return errors.Wrapf(err, "error determining path to directory %q", cliArgs[0])
}
contextDir = absDir
}
} else {
// No context directory or URL was specified. Try to use the
// home of the first locally-available Containerfile.
for i := range containerfiles {
if strings.HasPrefix(containerfiles[i], "http://") ||
strings.HasPrefix(containerfiles[i], "https://") ||
strings.HasPrefix(containerfiles[i], "git://") ||
strings.HasPrefix(containerfiles[i], "github.com/") {
continue
}
absFile, err := filepath.Abs(containerfiles[i])
if err != nil {
return errors.Wrapf(err, "error determining path to file %q", containerfiles[i])
}
contextDir = filepath.Dir(absFile)
break
}
}
if contextDir == "" {
return errors.Errorf("no context directory specified, and no containerfile specified")
}
if !fileIsDir(contextDir) {
return errors.Errorf("context must be a directory: %v", contextDir)
}
if len(containerfiles) == 0 {
if checkIfFileExists(filepath.Join(contextDir, "Containerfile")) {
containerfiles = append(containerfiles, filepath.Join(contextDir, "Containerfile"))
} else {
containerfiles = append(containerfiles, filepath.Join(contextDir, "Dockerfile"))
}
}
runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
runtimeFlags := []string{}
for _, arg := range c.RuntimeFlags {
runtimeFlags = append(runtimeFlags, "--"+arg)
}
conf, err := runtime.GetConfig()
if err != nil {
return err
}
if conf != nil && conf.CgroupManager == define.SystemdCgroupsManager {
runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
}
// end from buildah
defer runtime.DeferredShutdown(false)
var stdout, stderr, reporter *os.File
stdout = os.Stdout
stderr = os.Stderr
reporter = os.Stderr
if c.Flag("logfile").Changed {
f, err := os.OpenFile(c.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return errors.Errorf("error opening logfile %q: %v", c.Logfile, err)
}
defer f.Close()
logrus.SetOutput(f)
stdout = f
stderr = f
reporter = f
}
var memoryLimit, memorySwap int64
if c.Flags().Changed("memory") {
memoryLimit, err = units.RAMInBytes(c.Memory)
if err != nil {
return err
}
}
if c.Flags().Changed("memory-swap") {
memorySwap, err = units.RAMInBytes(c.MemorySwap)
if err != nil {
return err
}
}
nsValues, err := getNsValues(c)
if err != nil {
return err
}
buildOpts := buildah.CommonBuildOptions{
AddHost: c.AddHost,
CgroupParent: c.CgroupParent,
CPUPeriod: c.CPUPeriod,
CPUQuota: c.CPUQuota,
CPUShares: c.CPUShares,
CPUSetCPUs: c.CPUSetCPUs,
CPUSetMems: c.CPUSetMems,
Memory: memoryLimit,
MemorySwap: memorySwap,
ShmSize: c.ShmSize,
Ulimit: c.Ulimit,
Volumes: c.Volumes,
}
// `buildah bud --layers=false` acts like `docker build --squash` does.
// That is all of the new layers created during the build process are
// condensed into one, any layers present prior to this build are retained
// without condensing. `buildah bud --squash` squashes both new and old
// layers down into one. Translate Podman commands into Buildah.
// Squash invoked, retain old layers, squash new layers into one.
if c.Flags().Changed("squash") && c.Squash {
c.Squash = false
layers = false
}
// Squash-all invoked, squash both new and old layers into one.
if c.Flags().Changed("squash-all") {
c.Squash = true
layers = false
}
options := imagebuildah.BuildOptions{
Architecture: c.Arch,
CommonBuildOpts: &buildOpts,
AdditionalTags: tags,
Annotations: c.Annotation,
Args: args,
CNIConfigDir: c.CNIConfigDir,
CNIPluginPath: c.CNIPlugInPath,
Compression: imagebuildah.Gzip,
ContextDirectory: contextDir,
DefaultMountsFilePath: c.GlobalFlags.DefaultMountsFile,
Err: stderr,
In: os.Stdin,
ForceRmIntermediateCtrs: c.ForceRm,
IIDFile: c.Iidfile,
Labels: c.Label,
Layers: layers,
NamespaceOptions: nsValues,
NoCache: c.NoCache,
OS: c.OS,
Out: stdout,
Output: output,
OutputFormat: format,
PullPolicy: pullPolicy,
Quiet: c.Quiet,
RemoveIntermediateCtrs: c.Rm,
ReportWriter: reporter,
RuntimeArgs: runtimeFlags,
SignaturePolicyPath: c.SignaturePolicy,
Squash: c.Squash,
SystemContext: &types.SystemContext{
OSChoice: c.OverrideOS,
ArchitectureChoice: c.OverrideArch,
},
Target: c.Target,
}
_, _, err = runtime.Build(getContext(), c, options, containerfiles)
return err
}
// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false"
// otherwise it returns true
func useLayers() string {
layers := os.Getenv("BUILDAH_LAYERS")
if strings.ToLower(layers) == "false" || layers == "0" {
return "false"
}
return "true"
}