219 lines
7.5 KiB
Go
219 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/buildah/imagebuildah"
|
|
buildahcli "github.com/containers/buildah/pkg/cli"
|
|
"github.com/containers/buildah/pkg/parse"
|
|
"github.com/containers/buildah/util"
|
|
"github.com/containers/common/pkg/auth"
|
|
"github.com/containers/image/v5/storage"
|
|
"github.com/containers/image/v5/transports/alltransports"
|
|
"github.com/containers/image/v5/types"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type commitInputOptions struct {
|
|
authfile string
|
|
blobCache string
|
|
certDir string
|
|
creds string
|
|
disableCompression bool
|
|
format string
|
|
iidfile string
|
|
omitTimestamp bool
|
|
quiet bool
|
|
referenceTime string
|
|
rm bool
|
|
signaturePolicy string
|
|
signBy string
|
|
squash bool
|
|
tlsVerify bool
|
|
encryptionKeys []string
|
|
encryptLayers []int
|
|
}
|
|
|
|
func init() {
|
|
var (
|
|
opts commitInputOptions
|
|
commitDescription = "\n Writes a new image using the container's read-write layer and, if it is based\n on an image, the layers of that image."
|
|
)
|
|
commitCommand := &cobra.Command{
|
|
Use: "commit",
|
|
Short: "Create an image from a working container",
|
|
Long: commitDescription,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return commitCmd(cmd, args, opts)
|
|
},
|
|
Example: `buildah commit containerID
|
|
buildah commit containerID newImageName
|
|
buildah commit containerID docker://localhost:5000/imageId`,
|
|
}
|
|
commitCommand.SetUsageTemplate(UsageTemplate())
|
|
flags := commitCommand.Flags()
|
|
flags.SetInterspersed(false)
|
|
|
|
flags.StringVar(&opts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
|
flags.StringVar(&opts.blobCache, "blob-cache", "", "assume image blobs in the specified directory will be available for pushing")
|
|
flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", nil, "key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)")
|
|
flags.IntSliceVar(&opts.encryptLayers, "encrypt-layer", nil, "layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified")
|
|
|
|
if err := flags.MarkHidden("blob-cache"); err != nil {
|
|
panic(fmt.Sprintf("error marking blob-cache as hidden: %v", err))
|
|
}
|
|
|
|
flags.StringVar(&opts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry")
|
|
flags.StringVar(&opts.creds, "creds", "", "use `[username[:password]]` for accessing the registry")
|
|
flags.BoolVarP(&opts.disableCompression, "disable-compression", "D", true, "don't compress layers")
|
|
flags.StringVarP(&opts.format, "format", "f", defaultFormat(), "`format` of the image manifest and metadata")
|
|
flags.StringVar(&opts.iidfile, "iidfile", "", "Write the image ID to the file")
|
|
flags.BoolVar(&opts.omitTimestamp, "omit-timestamp", false, "set created timestamp to epoch 0 to allow for deterministic builds")
|
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when writing images")
|
|
flags.StringVar(&opts.referenceTime, "reference-time", "", "set the timestamp on the image to match the named `file`")
|
|
flags.StringVar(&opts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`")
|
|
|
|
if err := flags.MarkHidden("reference-time"); err != nil {
|
|
panic(fmt.Sprintf("error marking reference-time as hidden: %v", err))
|
|
}
|
|
|
|
flags.BoolVar(&opts.rm, "rm", false, "remove the container and its content after committing it to an image. Default leaves the container and its content in place.")
|
|
flags.StringVar(&opts.signaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)")
|
|
|
|
if err := flags.MarkHidden("signature-policy"); err != nil {
|
|
panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err))
|
|
}
|
|
|
|
flags.BoolVar(&opts.squash, "squash", false, "produce an image with only one layer")
|
|
flags.BoolVar(&opts.tlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when accessing the registry")
|
|
|
|
rootCmd.AddCommand(commitCommand)
|
|
|
|
}
|
|
|
|
func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error {
|
|
var dest types.ImageReference
|
|
if len(args) == 0 {
|
|
return errors.Errorf("container ID must be specified")
|
|
}
|
|
if err := buildahcli.VerifyFlagsArgsOrder(args); err != nil {
|
|
return err
|
|
}
|
|
if err := auth.CheckAuthFile(iopts.authfile); err != nil {
|
|
return err
|
|
}
|
|
|
|
name := args[0]
|
|
args = Tail(args)
|
|
if len(args) > 1 {
|
|
return errors.Errorf("too many arguments specified")
|
|
}
|
|
image := ""
|
|
if len(args) > 0 {
|
|
image = args[0]
|
|
}
|
|
compress := imagebuildah.Gzip
|
|
if iopts.disableCompression {
|
|
compress = imagebuildah.Uncompressed
|
|
}
|
|
timestamp := time.Now().UTC()
|
|
if c.Flag("reference-time").Changed {
|
|
referenceFile := iopts.referenceTime
|
|
finfo, err := os.Stat(referenceFile)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error reading timestamp of file %q", referenceFile)
|
|
}
|
|
timestamp = finfo.ModTime().UTC()
|
|
}
|
|
|
|
format, err := getFormat(iopts.format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store, err := getStore(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := getContext()
|
|
|
|
builder, err := openBuilder(ctx, store, name)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error reading build container %q", name)
|
|
}
|
|
|
|
systemContext, err := parse.SystemContextFromOptions(c)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error building system context")
|
|
}
|
|
|
|
if image != "" {
|
|
if dest, err = alltransports.ParseImageName(image); err != nil {
|
|
candidates, _, _, err := util.ResolveName(image, "", systemContext, store)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing target image name %q", image)
|
|
}
|
|
if len(candidates) == 0 {
|
|
return errors.Errorf("error parsing target image name %q", image)
|
|
}
|
|
dest2, err2 := storage.Transport.ParseStoreReference(store, candidates[0])
|
|
if err2 != nil {
|
|
return errors.Wrapf(err, "error parsing target image name %q", image)
|
|
}
|
|
dest = dest2
|
|
}
|
|
}
|
|
|
|
// Add builder identity information.
|
|
builder.SetLabel(buildah.BuilderIdentityAnnotation, buildah.Version)
|
|
|
|
encConfig, encLayers, err := getEncryptConfig(iopts.encryptionKeys, iopts.encryptLayers)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to obtain encryption config")
|
|
}
|
|
|
|
options := buildah.CommitOptions{
|
|
PreferredManifestType: format,
|
|
Compression: compress,
|
|
SignaturePolicyPath: iopts.signaturePolicy,
|
|
HistoryTimestamp: ×tamp,
|
|
SystemContext: systemContext,
|
|
IIDFile: iopts.iidfile,
|
|
Squash: iopts.squash,
|
|
BlobDirectory: iopts.blobCache,
|
|
OmitTimestamp: iopts.omitTimestamp,
|
|
SignBy: iopts.signBy,
|
|
OciEncryptConfig: encConfig,
|
|
OciEncryptLayers: encLayers,
|
|
}
|
|
if !iopts.quiet {
|
|
options.ReportWriter = os.Stderr
|
|
}
|
|
id, ref, _, err := builder.Commit(ctx, dest, options)
|
|
if err != nil {
|
|
return util.GetFailureCause(err, errors.Wrapf(err, "error committing container %q to %q", builder.Container, image))
|
|
}
|
|
if ref != nil && id != "" {
|
|
logrus.Debugf("wrote image %s with ID %s", ref, id)
|
|
} else if ref != nil {
|
|
logrus.Debugf("wrote image %s", ref)
|
|
} else if id != "" {
|
|
logrus.Debugf("wrote image with ID %s", id)
|
|
} else {
|
|
logrus.Debugf("wrote image")
|
|
}
|
|
if options.IIDFile == "" && id != "" {
|
|
fmt.Printf("%s\n", id)
|
|
}
|
|
|
|
if iopts.rm {
|
|
return builder.Delete()
|
|
}
|
|
return nil
|
|
}
|