-
Notifications
You must be signed in to change notification settings - Fork 757
/
push.go
271 lines (241 loc) · 9.94 KB
/
push.go
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package main
import (
"fmt"
"os"
"strings"
"time"
"errors"
"github.com/containers/buildah"
"github.com/containers/buildah/define"
"github.com/containers/buildah/pkg/cli"
"github.com/containers/buildah/pkg/parse"
util "github.com/containers/buildah/util"
"github.com/containers/common/pkg/auth"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/compression"
"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/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
compressionFormat string
compressionLevel int
forceCompressionFormat bool
retry int
retryDelay string
rm bool
quiet bool
removeSignatures bool
signaturePolicy string
signBy string
tlsVerify bool
encryptionKeys []string
encryptLayers []int
insecure bool
addCompression []string
}
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.BoolVarP(&opts.forceCompressionFormat, "force-compression", "", false, "use the specified compression algorithm if the destination contains a differently-compressed variant already")
flags.StringVarP(&opts.format, "format", "f", "", "manifest type (oci, v2s1, or v2s2) to use in the destination (default is manifest type of source, with fallbacks)")
flags.StringVar(&opts.compressionFormat, "compression-format", "", "compression format to use")
flags.IntVar(&opts.compressionLevel, "compression-level", 0, "compression level to use")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when pushing images")
flags.IntVar(&opts.retry, "retry", int(defaultContainerConfig.Engine.Retry), "number of times to retry in case of failure when performing push")
flags.StringVar(&opts.retryDelay, "retry-delay", defaultContainerConfig.Engine.RetryDelay, "delay between retries in case of push failures")
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 := cli.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 fmt.Errorf(`invalid image name "%s"`, args[0])
}
default:
return errors.New("Only two arguments are necessary to push: source and destination")
}
compress := define.Gzip
if iopts.disableCompression {
compress = define.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 fmt.Errorf("building system context: %w", err)
}
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 fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", iopts.format)
}
}
encConfig, encLayers, err := cli.EncryptConfig(iopts.encryptionKeys, iopts.encryptLayers)
if err != nil {
return fmt.Errorf("unable to obtain encryption config: %w", err)
}
if c.Flag("compression-format").Changed {
if !c.Flag("force-compression").Changed {
// If `compression-format` is set and no value for `--force-compression`
// is selected then defaults to `true`.
iopts.forceCompressionFormat = true
}
}
options := buildah.PushOptions{
Compression: compress,
ManifestType: manifestType,
SignaturePolicyPath: iopts.signaturePolicy,
Store: store,
SystemContext: systemContext,
BlobDirectory: iopts.blobCache,
RemoveSignatures: iopts.removeSignatures,
SignBy: iopts.signBy,
MaxRetries: iopts.retry,
OciEncryptConfig: encConfig,
OciEncryptLayers: encLayers,
ForceCompressionFormat: iopts.forceCompressionFormat,
}
if iopts.retryDelay != "" {
options.RetryDelay, err = time.ParseDuration(iopts.retryDelay)
if err != nil {
return fmt.Errorf("unable to parse value provided %q as --retry-delay: %w", iopts.retryDelay, err)
}
}
if !iopts.quiet {
options.ReportWriter = os.Stderr
}
if iopts.compressionFormat != "" {
algo, err := compression.AlgorithmByName(iopts.compressionFormat)
if err != nil {
return err
}
options.CompressionFormat = &algo
}
if c.Flag("compression-level").Changed {
options.CompressionLevel = &iopts.compressionLevel
}
ref, digest, err := buildah.Push(getContext(), src, dest, options)
if err != nil {
if !errors.Is(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, fmt.Errorf("pushing image %q to %q: %w", src, destSpec, err))
}
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 = os.WriteFile(iopts.digestfile, []byte(digest.String()), 0644); err != nil {
return util.GetFailureCause(err, fmt.Errorf("failed to write digest to file %q: %w", iopts.digestfile, err))
}
}
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)
}