github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/build.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/containers/buildah"
    10  	"github.com/containers/buildah/imagebuildah"
    11  	buildahcli "github.com/containers/buildah/pkg/cli"
    12  	"github.com/containers/buildah/pkg/parse"
    13  	"github.com/containers/common/pkg/config"
    14  	"github.com/containers/libpod/cmd/podman/cliconfig"
    15  	"github.com/containers/libpod/pkg/adapter"
    16  	"github.com/docker/go-units"
    17  	"github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  	"github.com/spf13/cobra"
    21  	"github.com/spf13/pflag"
    22  )
    23  
    24  var (
    25  	buildCommand     cliconfig.BuildValues
    26  	buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory."
    27  	layerValues      buildahcli.LayerResults
    28  	budFlagsValues   buildahcli.BudResults
    29  	fromAndBudValues buildahcli.FromAndBudResults
    30  	userNSValues     buildahcli.UserNSResults
    31  	namespaceValues  buildahcli.NameSpaceResults
    32  	podBuildValues   cliconfig.PodmanBuildResults
    33  
    34  	_buildCommand = &cobra.Command{
    35  		Use:   "build [flags] CONTEXT",
    36  		Short: "Build an image using instructions from Containerfiles",
    37  		Long:  buildDescription,
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			buildCommand.InputArgs = args
    40  			buildCommand.GlobalFlags = MainGlobalOpts
    41  			buildCommand.BudResults = &budFlagsValues
    42  			buildCommand.UserNSResults = &userNSValues
    43  			buildCommand.FromAndBudResults = &fromAndBudValues
    44  			buildCommand.LayerResults = &layerValues
    45  			buildCommand.NameSpaceResults = &namespaceValues
    46  			buildCommand.PodmanBuildResults = &podBuildValues
    47  			buildCommand.Remote = remoteclient
    48  			return buildCmd(&buildCommand)
    49  		},
    50  		Example: `podman build .
    51    podman build --creds=username:password -t imageName -f Containerfile.simple .
    52    podman build --layers --force-rm --tag imageName .`,
    53  	}
    54  )
    55  
    56  func initBuild() {
    57  	buildCommand.Command = _buildCommand
    58  	buildCommand.SetHelpTemplate(HelpTemplate())
    59  	buildCommand.SetUsageTemplate(UsageTemplate())
    60  	flags := buildCommand.Flags()
    61  	flags.SetInterspersed(true)
    62  	budFlags := buildahcli.GetBudFlags(&budFlagsValues)
    63  	flag := budFlags.Lookup("pull")
    64  	if err := flag.Value.Set("true"); err != nil {
    65  		logrus.Error("unable to set pull flag to true")
    66  	}
    67  	flag.DefValue = "true"
    68  	layerFlags := buildahcli.GetLayerFlags(&layerValues)
    69  	flag = layerFlags.Lookup("layers")
    70  	if err := flag.Value.Set(useLayers()); err != nil {
    71  		logrus.Error("unable to set uselayers")
    72  	}
    73  	flag.DefValue = useLayers()
    74  	flag = layerFlags.Lookup("force-rm")
    75  	if err := flag.Value.Set("true"); err != nil {
    76  		logrus.Error("unable to set force-rm flag to true")
    77  	}
    78  	flag.DefValue = "true"
    79  	podmanBuildFlags := GetPodmanBuildFlags(&podBuildValues)
    80  	flag = podmanBuildFlags.Lookup("squash-all")
    81  	if err := flag.Value.Set("false"); err != nil {
    82  		logrus.Error("unable to set squash-all flag to false")
    83  	}
    84  
    85  	flag.DefValue = "true"
    86  	fromAndBugFlags, err := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues)
    87  	if err != nil {
    88  		logrus.Errorf("failed to setup podman build flags: %v", err)
    89  		os.Exit(1)
    90  	}
    91  
    92  	flags.AddFlagSet(&budFlags)
    93  	flags.AddFlagSet(&fromAndBugFlags)
    94  	flags.AddFlagSet(&layerFlags)
    95  	flags.AddFlagSet(&podmanBuildFlags)
    96  	markFlagHidden(flags, "signature-policy")
    97  }
    98  
    99  // GetPodmanBuildFlags flags used only by `podman build` and not by
   100  // `buildah bud`.
   101  func GetPodmanBuildFlags(flags *cliconfig.PodmanBuildResults) pflag.FlagSet {
   102  	fs := pflag.FlagSet{}
   103  	fs.BoolVar(&flags.SquashAll, "squash-all", false, "Squash all layers into a single layer.")
   104  	return fs
   105  }
   106  
   107  func getContainerfiles(files []string) []string {
   108  	var containerfiles []string
   109  	for _, f := range files {
   110  		if f == "-" {
   111  			containerfiles = append(containerfiles, "/dev/stdin")
   112  		} else {
   113  			containerfiles = append(containerfiles, f)
   114  		}
   115  	}
   116  	return containerfiles
   117  }
   118  
   119  func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) {
   120  	var ret []buildah.NamespaceOption
   121  	if c.Network != "" {
   122  		switch {
   123  		case c.Network == "host":
   124  			ret = append(ret, buildah.NamespaceOption{
   125  				Name: string(specs.NetworkNamespace),
   126  				Host: true,
   127  			})
   128  		case c.Network == "container":
   129  			ret = append(ret, buildah.NamespaceOption{
   130  				Name: string(specs.NetworkNamespace),
   131  			})
   132  		case c.Network[0] == '/':
   133  			ret = append(ret, buildah.NamespaceOption{
   134  				Name: string(specs.NetworkNamespace),
   135  				Path: c.Network,
   136  			})
   137  		default:
   138  			return nil, fmt.Errorf("unsupported configuration network=%s", c.Network)
   139  		}
   140  	}
   141  	return ret, nil
   142  }
   143  
   144  func buildCmd(c *cliconfig.BuildValues) error {
   145  	if (c.Flags().Changed("squash") && c.Flags().Changed("layers")) ||
   146  		(c.Flags().Changed("squash-all") && c.Flags().Changed("layers")) ||
   147  		(c.Flags().Changed("squash-all") && c.Flags().Changed("squash")) {
   148  		return fmt.Errorf("cannot specify squash, squash-all and layers options together")
   149  	}
   150  
   151  	// The following was taken directly from containers/buildah/cmd/bud.go
   152  	// TODO Find a away to vendor more of this in rather than copy from bud
   153  	output := ""
   154  	tags := []string{}
   155  	if c.Flag("tag").Changed {
   156  		tags = c.Tag
   157  		if len(tags) > 0 {
   158  			output = tags[0]
   159  			tags = tags[1:]
   160  		}
   161  	}
   162  	if c.BudResults.Authfile != "" {
   163  		if _, err := os.Stat(c.BudResults.Authfile); err != nil {
   164  			return errors.Wrapf(err, "error getting authfile %s", c.BudResults.Authfile)
   165  		}
   166  	}
   167  
   168  	pullPolicy := imagebuildah.PullNever
   169  	if c.Pull {
   170  		pullPolicy = imagebuildah.PullIfMissing
   171  	}
   172  	if c.PullAlways {
   173  		pullPolicy = imagebuildah.PullAlways
   174  	}
   175  
   176  	args := make(map[string]string)
   177  	if c.Flag("build-arg").Changed {
   178  		for _, arg := range c.BuildArg {
   179  			av := strings.SplitN(arg, "=", 2)
   180  			if len(av) > 1 {
   181  				args[av[0]] = av[1]
   182  			} else {
   183  				delete(args, av[0])
   184  			}
   185  		}
   186  	}
   187  
   188  	containerfiles := getContainerfiles(c.File)
   189  	format, err := getFormat(&c.PodmanCommand)
   190  	if err != nil {
   191  		return nil
   192  	}
   193  	contextDir := ""
   194  	cliArgs := c.InputArgs
   195  
   196  	layers := c.Layers // layers for podman defaults to true
   197  	// Check to see if the BUILDAH_LAYERS environment variable is set and override command-line
   198  	if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok {
   199  		layers = buildahcli.UseLayers()
   200  	}
   201  
   202  	if len(cliArgs) > 0 {
   203  		// The context directory could be a URL.  Try to handle that.
   204  		tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", cliArgs[0])
   205  		if err != nil {
   206  			return errors.Wrapf(err, "error prepping temporary context directory")
   207  		}
   208  		if tempDir != "" {
   209  			// We had to download it to a temporary directory.
   210  			// Delete it later.
   211  			defer func() {
   212  				if err = os.RemoveAll(tempDir); err != nil {
   213  					logrus.Errorf("error removing temporary directory %q: %v", contextDir, err)
   214  				}
   215  			}()
   216  			contextDir = filepath.Join(tempDir, subDir)
   217  		} else {
   218  			// Nope, it was local.  Use it as is.
   219  			absDir, err := filepath.Abs(cliArgs[0])
   220  			if err != nil {
   221  				return errors.Wrapf(err, "error determining path to directory %q", cliArgs[0])
   222  			}
   223  			contextDir = absDir
   224  		}
   225  	} else {
   226  		// No context directory or URL was specified.  Try to use the
   227  		// home of the first locally-available Containerfile.
   228  		for i := range containerfiles {
   229  			if strings.HasPrefix(containerfiles[i], "http://") ||
   230  				strings.HasPrefix(containerfiles[i], "https://") ||
   231  				strings.HasPrefix(containerfiles[i], "git://") ||
   232  				strings.HasPrefix(containerfiles[i], "github.com/") {
   233  				continue
   234  			}
   235  			absFile, err := filepath.Abs(containerfiles[i])
   236  			if err != nil {
   237  				return errors.Wrapf(err, "error determining path to file %q", containerfiles[i])
   238  			}
   239  			contextDir = filepath.Dir(absFile)
   240  			break
   241  		}
   242  	}
   243  	if contextDir == "" {
   244  		return errors.Errorf("no context directory specified, and no containerfile specified")
   245  	}
   246  	if !fileIsDir(contextDir) {
   247  		return errors.Errorf("context must be a directory: %v", contextDir)
   248  	}
   249  	if len(containerfiles) == 0 {
   250  		if checkIfFileExists(filepath.Join(contextDir, "Containerfile")) {
   251  			containerfiles = append(containerfiles, filepath.Join(contextDir, "Containerfile"))
   252  		} else {
   253  			containerfiles = append(containerfiles, filepath.Join(contextDir, "Dockerfile"))
   254  		}
   255  	}
   256  
   257  	runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
   258  	if err != nil {
   259  		return errors.Wrapf(err, "could not get runtime")
   260  	}
   261  
   262  	runtimeFlags := []string{}
   263  	for _, arg := range c.RuntimeFlags {
   264  		runtimeFlags = append(runtimeFlags, "--"+arg)
   265  	}
   266  
   267  	conf, err := runtime.GetConfig()
   268  	if err != nil {
   269  		return err
   270  	}
   271  	if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager {
   272  		runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
   273  	}
   274  	// end from buildah
   275  
   276  	defer runtime.DeferredShutdown(false)
   277  
   278  	var stdin, stdout, stderr, reporter *os.File
   279  	stdin = os.Stdin
   280  	stdout = os.Stdout
   281  	stderr = os.Stderr
   282  	reporter = os.Stderr
   283  	if c.Flag("logfile").Changed {
   284  		f, err := os.OpenFile(c.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
   285  		if err != nil {
   286  			return errors.Errorf("error opening logfile %q: %v", c.Logfile, err)
   287  		}
   288  		defer f.Close()
   289  		logrus.SetOutput(f)
   290  		stdout = f
   291  		stderr = f
   292  		reporter = f
   293  	}
   294  
   295  	var memoryLimit, memorySwap int64
   296  	if c.Flags().Changed("memory") {
   297  		memoryLimit, err = units.RAMInBytes(c.Memory)
   298  		if err != nil {
   299  			return err
   300  		}
   301  	}
   302  
   303  	if c.Flags().Changed("memory-swap") {
   304  		memorySwap, err = units.RAMInBytes(c.MemorySwap)
   305  		if err != nil {
   306  			return err
   307  		}
   308  	}
   309  
   310  	nsValues, err := getNsValues(c)
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	networkPolicy := buildah.NetworkDefault
   316  	for _, ns := range nsValues {
   317  		if ns.Name == "none" {
   318  			networkPolicy = buildah.NetworkDisabled
   319  			break
   320  		} else if !filepath.IsAbs(ns.Path) {
   321  			networkPolicy = buildah.NetworkEnabled
   322  			break
   323  		}
   324  	}
   325  
   326  	buildOpts := buildah.CommonBuildOptions{
   327  		AddHost:      c.AddHost,
   328  		CgroupParent: c.CgroupParent,
   329  		CPUPeriod:    c.CPUPeriod,
   330  		CPUQuota:     c.CPUQuota,
   331  		CPUShares:    c.CPUShares,
   332  		CPUSetCPUs:   c.CPUSetCPUs,
   333  		CPUSetMems:   c.CPUSetMems,
   334  		Memory:       memoryLimit,
   335  		MemorySwap:   memorySwap,
   336  		ShmSize:      c.ShmSize,
   337  		Ulimit:       c.Ulimit,
   338  		Volumes:      c.Volumes,
   339  	}
   340  
   341  	// `buildah bud --layers=false` acts like `docker build --squash` does.
   342  	// That is all of the new layers created during the build process are
   343  	// condensed into one, any layers present prior to this build are retained
   344  	// without condensing.  `buildah bud --squash` squashes both new and old
   345  	// layers down into one.  Translate Podman commands into Buildah.
   346  	// Squash invoked, retain old layers, squash new layers into one.
   347  	if c.Flags().Changed("squash") && c.Squash {
   348  		c.Squash = false
   349  		layers = false
   350  	}
   351  	// Squash-all invoked, squash both new and old layers into one.
   352  	if c.Flags().Changed("squash-all") {
   353  		c.Squash = true
   354  		layers = false
   355  	}
   356  
   357  	compression := imagebuildah.Gzip
   358  	if c.DisableCompression {
   359  		compression = imagebuildah.Uncompressed
   360  	}
   361  
   362  	isolation, err := parse.IsolationOption(c.Isolation)
   363  	if err != nil {
   364  		return errors.Wrapf(err, "error parsing ID mapping options")
   365  	}
   366  
   367  	usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, isolation)
   368  	if err != nil {
   369  		return errors.Wrapf(err, "error parsing ID mapping options")
   370  	}
   371  	nsValues = append(nsValues, usernsOption...)
   372  
   373  	systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command)
   374  	if err != nil {
   375  		return errors.Wrapf(err, "error building system context")
   376  	}
   377  
   378  	options := imagebuildah.BuildOptions{
   379  		AddCapabilities:         c.CapAdd,
   380  		AdditionalTags:          tags,
   381  		Annotations:             c.Annotation,
   382  		Architecture:            c.Arch,
   383  		Args:                    args,
   384  		BlobDirectory:           c.BlobCache,
   385  		CNIConfigDir:            c.CNIConfigDir,
   386  		CNIPluginPath:           c.CNIPlugInPath,
   387  		CommonBuildOpts:         &buildOpts,
   388  		Compression:             compression,
   389  		ConfigureNetwork:        networkPolicy,
   390  		ContextDirectory:        contextDir,
   391  		DefaultMountsFilePath:   c.GlobalFlags.DefaultMountsFile,
   392  		Devices:                 c.Devices,
   393  		DropCapabilities:        c.CapDrop,
   394  		Err:                     stderr,
   395  		ForceRmIntermediateCtrs: c.ForceRm,
   396  		IDMappingOptions:        idmappingOptions,
   397  		IIDFile:                 c.Iidfile,
   398  		In:                      stdin,
   399  		Isolation:               isolation,
   400  		Labels:                  c.Label,
   401  		Layers:                  layers,
   402  		NamespaceOptions:        nsValues,
   403  		NoCache:                 c.NoCache,
   404  		OS:                      c.OS,
   405  		Out:                     stdout,
   406  		Output:                  output,
   407  		OutputFormat:            format,
   408  		PullPolicy:              pullPolicy,
   409  		Quiet:                   c.Quiet,
   410  		RemoveIntermediateCtrs:  c.Rm,
   411  		ReportWriter:            reporter,
   412  		RuntimeArgs:             runtimeFlags,
   413  		SignBy:                  c.SignBy,
   414  		SignaturePolicyPath:     c.SignaturePolicy,
   415  		Squash:                  c.Squash,
   416  		SystemContext:           systemContext,
   417  		Target:                  c.Target,
   418  		TransientMounts:         c.Volumes,
   419  	}
   420  	_, _, err = runtime.Build(getContext(), c, options, containerfiles)
   421  	return err
   422  }
   423  
   424  // useLayers returns false if BUILDAH_LAYERS is set to "0" or "false"
   425  // otherwise it returns true
   426  func useLayers() string {
   427  	layers := os.Getenv("BUILDAH_LAYERS")
   428  	if strings.ToLower(layers) == "false" || layers == "0" {
   429  		return "false"
   430  	}
   431  	return "true"
   432  }