1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"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/manifest"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/storage"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
type pushOptions struct {
all bool
authfile string
blobCache string
certDir string
creds string
digestfile string
disableCompression bool
format string
rm bool
quiet bool
removeSignatures bool
signaturePolicy string
signBy string
tlsVerify bool
encryptionKeys []string
encryptLayers []int
}
func init() {
var (
opts pushOptions
pushDescription = fmt.Sprintf(`
Pushes an image to a specified location.
The Image "DESTINATION" uses a "transport":"details" format. If not specified, will reuse source IMAGE as DESTINATION.
Supported transports:
%s
See buildah-push(1) section "DESTINATION" for the expected format
`, getListOfTransports())
)
pushCommand := &cobra.Command{
Use: "push",
Short: "Push an image to a specified destination",
Long: pushDescription,
RunE: func(cmd *cobra.Command, args []string) error {
return pushCmd(cmd, args, opts)
},
Example: `buildah push imageID docker://registry.example.com/repository:tag
buildah push imageID docker-daemon:image:tagi
buildah push imageID oci:/path/to/layout:image:tag`,
}
pushCommand.SetUsageTemplate(UsageTemplate())
flags := pushCommand.Flags()
flags.SetInterspersed(false)
flags.BoolVar(&opts.all, "all", false, "push all of the images referenced by the manifest list")
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.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.StringVar(&opts.digestfile, "digestfile", "", "after copying the image, write the digest of the resulting image to the file")
flags.BoolVarP(&opts.disableCompression, "disable-compression", "D", false, "don't compress layers")
flags.StringVarP(&opts.format, "format", "f", "", "manifest type (oci, v2s1, or v2s2) to use when saving image using the 'dir:' transport (default is manifest type of source)")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when pushing images")
flags.BoolVar(&opts.rm, "rm", false, "remove the manifest list if push succeeds")
flags.BoolVarP(&opts.removeSignatures, "remove-signatures", "", false, "don't copy signatures when pushing image")
flags.StringVar(&opts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`")
flags.StringVar(&opts.signaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)")
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("signature-policy"); err != nil {
panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err))
}
flags.BoolVar(&opts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.")
if err := flags.MarkHidden("blob-cache"); err != nil {
panic(fmt.Sprintf("error marking blob-cache as hidden: %v", err))
}
rootCmd.AddCommand(pushCommand)
}
func pushCmd(c *cobra.Command, args []string, iopts pushOptions) error {
var src, destSpec string
if err := buildahcli.VerifyFlagsArgsOrder(args); err != nil {
return err
}
if err := auth.CheckAuthFile(iopts.authfile); err != nil {
return err
}
switch len(args) {
case 0:
return errors.New("At least a source image ID must be specified")
case 1:
src = args[0]
destSpec = src
logrus.Debugf("Destination argument not specified, assuming the same as the source: %s", destSpec)
case 2:
src = args[0]
destSpec = args[1]
if src == "" {
return errors.Errorf(`Invalid image name "%s"`, args[0])
}
default:
return errors.New("Only two arguments are necessary to push: source and destination")
}
compress := imagebuildah.Gzip
if iopts.disableCompression {
compress = imagebuildah.Uncompressed
}
store, err := getStore(c)
if err != nil {
return err
}
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)
}
systemContext, err := parse.SystemContextFromOptions(c)
if err != nil {
return errors.Wrapf(err, "error building system context")
}
var manifestType string
if iopts.format != "" {
switch iopts.format {
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
case "v2s1":
manifestType = manifest.DockerV2Schema1SignedMediaType
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", iopts.format)
}
}
encConfig, encLayers, err := getEncryptConfig(iopts.encryptionKeys, iopts.encryptLayers)
if err != nil {
return errors.Wrapf(err, "unable to obtain encryption config")
}
options := buildah.PushOptions{
Compression: compress,
ManifestType: manifestType,
SignaturePolicyPath: iopts.signaturePolicy,
Store: store,
SystemContext: systemContext,
BlobDirectory: iopts.blobCache,
RemoveSignatures: iopts.removeSignatures,
SignBy: iopts.signBy,
MaxRetries: maxPullPushRetries,
RetryDelay: pullPushRetryDelay,
OciEncryptConfig: encConfig,
OciEncryptLayers: encLayers,
}
if !iopts.quiet {
options.ReportWriter = os.Stderr
}
ref, digest, err := buildah.Push(getContext(), src, dest, options)
if err != nil {
if errors.Cause(err) != storage.ErrImageUnknown {
// Image might be a manifest so attempt a manifest push
if manifestsErr := manifestPush(systemContext, store, src, destSpec, iopts); manifestsErr == nil {
return nil
}
}
return util.GetFailureCause(err, errors.Wrapf(err, "error pushing image %q to %q", src, destSpec))
}
if ref != nil {
logrus.Debugf("pushed image %q with digest %s", ref, digest.String())
} else {
logrus.Debugf("pushed image with digest %s", digest.String())
}
logrus.Debugf("Successfully pushed %s with digest %s", transports.ImageName(dest), digest.String())
if iopts.digestfile != "" {
if err = ioutil.WriteFile(iopts.digestfile, []byte(digest.String()), 0644); err != nil {
return util.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", iopts.digestfile))
}
}
return nil
}
// getListOfTransports gets the transports supported from the image library
// and strips of the "tarball" transport from the string of transports returned
func getListOfTransports() string {
allTransports := strings.Join(transports.ListNames(), ",")
return strings.Replace(allTransports, ",tarball", "", 1)
}
|