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 }