crossplane

Форк
0
219 строк · 7.0 Кб
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
	"path/filepath"
22

23
	"github.com/google/go-containerregistry/pkg/name"
24
	v1 "github.com/google/go-containerregistry/pkg/v1"
25
	"github.com/google/go-containerregistry/pkg/v1/daemon"
26
	"github.com/google/go-containerregistry/pkg/v1/tarball"
27
	"github.com/spf13/afero"
28
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
	"k8s.io/apimachinery/pkg/runtime"
30

31
	"github.com/crossplane/crossplane-runtime/pkg/errors"
32
	"github.com/crossplane/crossplane-runtime/pkg/logging"
33
	"github.com/crossplane/crossplane-runtime/pkg/parser"
34

35
	"github.com/crossplane/crossplane/internal/xpkg"
36
	"github.com/crossplane/crossplane/internal/xpkg/parser/examples"
37
	"github.com/crossplane/crossplane/internal/xpkg/parser/yaml"
38
)
39

40
const (
41
	errGetNameFromMeta         = "failed to get package name from crossplane.yaml"
42
	errBuildPackage            = "failed to build package"
43
	errImageDigest             = "failed to get package digest"
44
	errCreatePackage           = "failed to create package file"
45
	errParseRuntimeImageRef    = "failed to parse runtime image reference"
46
	errPullRuntimeImage        = "failed to pull runtime image"
47
	errLoadRuntimeTarball      = "failed to load runtime tarball"
48
	errGetRuntimeBaseImageOpts = "failed to get runtime base image options"
49
)
50

51
// AfterApply constructs and binds context to any subcommands
52
// that have Run() methods that receive it.
53
func (c *buildCmd) AfterApply() error {
54
	c.fs = afero.NewOsFs()
55

56
	root, err := filepath.Abs(c.PackageRoot)
57
	if err != nil {
58
		return err
59
	}
60
	c.root = root
61

62
	ex, err := filepath.Abs(c.ExamplesRoot)
63
	if err != nil {
64
		return err
65
	}
66

67
	pp, err := yaml.New()
68
	if err != nil {
69
		return err
70
	}
71

72
	c.builder = xpkg.New(
73
		parser.NewFsBackend(
74
			c.fs,
75
			parser.FsDir(root),
76
			parser.FsFilters(
77
				append(
78
					buildFilters(root, c.Ignore),
79
					xpkg.SkipContains(c.ExamplesRoot))...),
80
		),
81
		parser.NewFsBackend(
82
			c.fs,
83
			parser.FsDir(ex),
84
			parser.FsFilters(
85
				buildFilters(ex, c.Ignore)...),
86
		),
87
		pp,
88
		examples.New(),
89
	)
90

91
	return nil
92
}
93

94
// buildCmd builds a crossplane package.
95
type buildCmd struct {
96
	// Flags. Keep sorted alphabetically.
97
	EmbedRuntimeImage        string   `placeholder:"NAME" help:"An OCI image to embed in the package as its runtime." xor:"runtime-image"`
98
	EmbedRuntimeImageTarball string   `placeholder:"PATH" type:"existingfile" help:"An OCI image tarball to embed in the package as its runtime." xor:"runtime-image"`
99
	ExamplesRoot             string   `short:"e" type:"path" help:"A directory of example YAML files to include in the package." default:"./examples"`
100
	Ignore                   []string `placeholder:"PATH" help:"Comma-separated file paths, specified relative to --package-root, to exclude from the package. Wildcards are supported. Directories cannot be excluded."`
101
	PackageFile              string   `short:"o" type:"path" placeholder:"PATH" help:"The file to write the package to. Defaults to a generated filename in --package-root."`
102
	PackageRoot              string   `short:"f" type:"existingdir" help:"The directory that contains the package's crossplane.yaml file." default:"."`
103

104
	// Internal state. These aren't part of the user-exposed CLI structure.
105
	fs      afero.Fs
106
	builder *xpkg.Builder
107
	root    string
108
}
109

110
func (c *buildCmd) Help() string {
111
	return `
112
This command builds a package file from a local directory of files.
113

114
Examples:
115

116
  # Build a package from the files in the 'package' directory.
117
  crossplane xpkg build --package-root=package/
118

119
  # Build a package that embeds a Provider's controller OCI image built with
120
  # 'docker build' so that the package can also be used to run the provider.
121
  # Provider and Function packages support embedding runtime images.
122
  crossplane xpkg build --embed-runtime-image=cc873e13cdc1
123
`
124
}
125

126
// GetRuntimeBaseImageOpts returns the controller base image options.
127
func (c *buildCmd) GetRuntimeBaseImageOpts() ([]xpkg.BuildOpt, error) {
128
	switch {
129
	case c.EmbedRuntimeImageTarball != "":
130
		img, err := tarball.ImageFromPath(filepath.Clean(c.EmbedRuntimeImageTarball), nil)
131
		if err != nil {
132
			return nil, errors.Wrap(err, errLoadRuntimeTarball)
133
		}
134
		return []xpkg.BuildOpt{xpkg.WithBase(img)}, nil
135
	case c.EmbedRuntimeImage != "":
136
		// We intentionally don't override the default registry here. Doing so
137
		// leads to unintuitive behavior, in that you can't tag your runtime
138
		// image as some/image:latest then pass that same tag to xpkg build.
139
		// Instead you'd need to pass index.docker.io/some/image:latest.
140
		ref, err := name.ParseReference(c.EmbedRuntimeImage)
141
		if err != nil {
142
			return nil, errors.Wrap(err, errParseRuntimeImageRef)
143
		}
144
		img, err := daemon.Image(ref, daemon.WithContext(context.Background()))
145
		if err != nil {
146
			return nil, errors.Wrap(err, errPullRuntimeImage)
147
		}
148
		return []xpkg.BuildOpt{xpkg.WithBase(img)}, nil
149
	}
150
	return nil, nil
151

152
}
153

154
// GetOutputFileName prepares output file name.
155
func (c *buildCmd) GetOutputFileName(meta runtime.Object, hash v1.Hash) (string, error) {
156
	output := filepath.Clean(c.PackageFile)
157
	if c.PackageFile == "" {
158
		pkgMeta, ok := meta.(metav1.Object)
159
		if !ok {
160
			return "", errors.New(errGetNameFromMeta)
161
		}
162
		pkgName := xpkg.FriendlyID(pkgMeta.GetName(), hash.Hex)
163
		output = xpkg.BuildPath(c.root, pkgName, xpkg.XpkgExtension)
164
	}
165
	return output, nil
166
}
167

168
// Run executes the build command.
169
func (c *buildCmd) Run(logger logging.Logger) error {
170
	var buildOpts []xpkg.BuildOpt
171
	rtBuildOpts, err := c.GetRuntimeBaseImageOpts()
172
	if err != nil {
173
		return errors.Wrap(err, errGetRuntimeBaseImageOpts)
174
	}
175
	buildOpts = append(buildOpts, rtBuildOpts...)
176

177
	img, meta, err := c.builder.Build(context.Background(), buildOpts...)
178
	if err != nil {
179
		return errors.Wrap(err, errBuildPackage)
180
	}
181

182
	hash, err := img.Digest()
183
	if err != nil {
184
		return errors.Wrap(err, errImageDigest)
185
	}
186

187
	output, err := c.GetOutputFileName(meta, hash)
188
	if err != nil {
189
		return err
190
	}
191

192
	f, err := c.fs.Create(output)
193
	if err != nil {
194
		return errors.Wrap(err, errCreatePackage)
195
	}
196

197
	defer func() { _ = f.Close() }()
198
	if err := tarball.Write(nil, img, f); err != nil {
199
		return err
200
	}
201
	logger.Info("xpkg saved", "output", output)
202
	return nil
203
}
204

205
// default build filters skip directories, empty files, and files without YAML
206
// extension in addition to any paths specified.
207
func buildFilters(root string, skips []string) []parser.FilterFn {
208
	defaultFns := []parser.FilterFn{
209
		parser.SkipDirs(),
210
		parser.SkipNotYAML(),
211
		parser.SkipEmpty(),
212
	}
213
	opts := make([]parser.FilterFn, len(skips)+len(defaultFns))
214
	copy(opts, defaultFns)
215
	for i, s := range skips {
216
		opts[i+len(defaultFns)] = parser.SkipPath(filepath.Join(root, s))
217
	}
218
	return opts
219
}
220

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

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

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

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