crossplane

Форк
0
215 строк · 6.9 Кб
1
/*
2
Copyright 2023 The Crossplane Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package xpkg
18

19
import (
20
	"context"
21
	"fmt"
22
	"os"
23
	"path/filepath"
24

25
	"github.com/google/go-containerregistry/pkg/authn"
26
	"github.com/google/go-containerregistry/pkg/name"
27
	v1 "github.com/google/go-containerregistry/pkg/v1"
28
	"github.com/google/go-containerregistry/pkg/v1/empty"
29
	"github.com/google/go-containerregistry/pkg/v1/mutate"
30
	"github.com/google/go-containerregistry/pkg/v1/remote"
31
	"github.com/google/go-containerregistry/pkg/v1/tarball"
32
	"github.com/spf13/afero"
33
	"golang.org/x/sync/errgroup"
34

35
	"github.com/crossplane/crossplane-runtime/pkg/errors"
36
	"github.com/crossplane/crossplane-runtime/pkg/logging"
37

38
	"github.com/crossplane/crossplane/internal/xpkg"
39
	"github.com/crossplane/crossplane/internal/xpkg/upbound"
40
	"github.com/crossplane/crossplane/internal/xpkg/upbound/credhelper"
41
)
42

43
const (
44
	errGetwd           = "failed to get working directory while searching for package"
45
	errFindPackageinWd = "failed to find a package in current working directory"
46
	errAnnotateLayers  = "failed to propagate xpkg annotations from OCI image config file to image layers"
47

48
	errFmtNewTag        = "failed to parse package tag %q"
49
	errFmtReadPackage   = "failed to read package file %s"
50
	errFmtPushPackage   = "failed to push package file %s"
51
	errFmtGetDigest     = "failed to get digest of package file %s"
52
	errFmtNewDigest     = "failed to parse digest %q for package file %s"
53
	errFmtGetMediaType  = "failed to get media type of package file %s"
54
	errFmtGetConfigFile = "failed to get OCI config file of package file %s"
55
	errFmtWriteIndex    = "failed to push an OCI image index of %d packages"
56
)
57

58
// pushCmd pushes a package.
59
type pushCmd struct {
60
	// Arguments.
61
	Package string `arg:"" help:"Where to push the package."`
62

63
	// Flags. Keep sorted alphabetically.
64
	PackageFiles []string `short:"f" type:"existingfile" placeholder:"PATH" help:"A comma-separated list of xpkg files to push."`
65

66
	// Common Upbound API configuration.
67
	upbound.Flags `embed:""`
68

69
	// Internal state. These aren't part of the user-exposed CLI structure.
70
	fs afero.Fs
71
}
72

73
func (c *pushCmd) Help() string {
74
	return `
75
Packages can be pushed to any OCI registry. Packages are pushed to the
76
xpkg.upbound.io registry by default. A package's OCI tag must be a semantic
77
version. Credentials for the registry are automatically retrieved from xpkg login 
78
and dockers configuration as fallback.
79

80
Examples:
81

82
  # Push a multi-platform package.
83
  crossplane xpkg push -f function-amd64.xpkg,function-arm64.xpkg crossplane/function-example:v1.0.0
84

85
  # Push the xpkg file in the current directory to a different registry.
86
  crossplane xpkg push index.docker.io/crossplane/function-example:v1.0.0
87
`
88
}
89

90
// AfterApply sets the tag for the parent push command.
91
func (c *pushCmd) AfterApply() error {
92
	c.fs = afero.NewOsFs()
93
	return nil
94
}
95

96
// Run runs the push cmd.
97
func (c *pushCmd) Run(logger logging.Logger) error { //nolint:gocyclo // This feels easier to read as-is.
98
	upCtx, err := upbound.NewFromFlags(c.Flags, upbound.AllowMissingProfile())
99
	if err != nil {
100
		return err
101
	}
102

103
	tag, err := name.NewTag(c.Package, name.WithDefaultRegistry(xpkg.DefaultRegistry))
104
	if err != nil {
105
		return errors.Wrapf(err, errFmtNewTag, c.Package)
106
	}
107

108
	// If package is not defined, attempt to find single package in current
109
	// directory.
110
	if len(c.PackageFiles) == 0 {
111
		wd, err := os.Getwd()
112
		if err != nil {
113
			return errors.Wrap(err, errGetwd)
114
		}
115
		path, err := xpkg.FindXpkgInDir(c.fs, wd)
116
		if err != nil {
117
			return errors.Wrap(err, errFindPackageinWd)
118
		}
119
		c.PackageFiles = []string{path}
120
		logger.Debug("Found package in directory", "path", path)
121
	}
122

123
	kc := authn.NewMultiKeychain(
124
		authn.NewKeychainFromHelper(credhelper.New(
125
			credhelper.WithLogger(logger),
126
			credhelper.WithProfile(upCtx.ProfileName),
127
			credhelper.WithDomain(upCtx.Domain.Hostname()),
128
		)),
129
		authn.DefaultKeychain,
130
	)
131

132
	// If there's only one package file, handle the simple path.
133
	if len(c.PackageFiles) == 1 {
134
		img, err := tarball.ImageFromPath(c.PackageFiles[0], nil)
135
		if err != nil {
136
			return errors.Wrapf(err, errFmtReadPackage, c.PackageFiles[0])
137
		}
138
		img, err = xpkg.AnnotateLayers(img)
139
		if err != nil {
140
			return errors.Wrapf(err, errAnnotateLayers)
141
		}
142
		if err := remote.Write(tag, img, remote.WithAuthFromKeychain(kc)); err != nil {
143
			return errors.Wrapf(err, errFmtPushPackage, c.PackageFiles[0])
144
		}
145
		logger.Debug("Pushed package", "path", c.PackageFiles[0], "ref", tag.String())
146
		return nil
147
	}
148

149
	// If there's more than one package file we'll write (push) them all by
150
	// their digest, and create an index with the specified tag. This pattern is
151
	// typically used to create a multi-platform image.
152
	adds := make([]mutate.IndexAddendum, len(c.PackageFiles))
153
	g, ctx := errgroup.WithContext(context.Background())
154
	for i, file := range c.PackageFiles {
155
		i, file := i, file // Pin range variables for use in goroutine
156
		g.Go(func() error {
157
			img, err := tarball.ImageFromPath(filepath.Clean(file), nil)
158
			if err != nil {
159
				return errors.Wrapf(err, errFmtReadPackage, file)
160
			}
161

162
			img, err = xpkg.AnnotateLayers(img)
163
			if err != nil {
164
				return errors.Wrapf(err, errAnnotateLayers)
165
			}
166

167
			d, err := img.Digest()
168
			if err != nil {
169
				return errors.Wrapf(err, errFmtGetDigest, file)
170
			}
171
			n := fmt.Sprintf("%s@%s", tag.Repository.Name(), d.String())
172
			ref, err := name.NewDigest(n, name.WithDefaultRegistry(xpkg.DefaultRegistry))
173
			if err != nil {
174
				return errors.Wrapf(err, errFmtNewDigest, n, file)
175
			}
176

177
			mt, err := img.MediaType()
178
			if err != nil {
179
				return errors.Wrapf(err, errFmtGetMediaType, file)
180
			}
181

182
			conf, err := img.ConfigFile()
183
			if err != nil {
184
				return errors.Wrapf(err, errFmtGetConfigFile, file)
185
			}
186

187
			adds[i] = mutate.IndexAddendum{
188
				Add: img,
189
				Descriptor: v1.Descriptor{
190
					MediaType: mt,
191
					Platform: &v1.Platform{
192
						Architecture: conf.Architecture,
193
						OS:           conf.OS,
194
						OSVersion:    conf.OSVersion,
195
					},
196
				},
197
			}
198
			if err := remote.Write(ref, img, remote.WithAuthFromKeychain(kc), remote.WithContext(ctx)); err != nil {
199
				return errors.Wrapf(err, errFmtPushPackage, file)
200
			}
201
			logger.Debug("Pushed package", "path", file, "ref", ref.String())
202
			return nil
203
		})
204
	}
205

206
	if err := g.Wait(); err != nil {
207
		return err
208
	}
209

210
	if err := remote.WriteIndex(tag, mutate.AppendManifests(empty.Index, adds...), remote.WithAuthFromKeychain(kc)); err != nil {
211
		return errors.Wrapf(err, errFmtWriteIndex, len(adds))
212
	}
213
	logger.Debug("Wrote OCI index", "ref", tag.String(), "manifests", len(adds))
214
	return nil
215
}
216

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.