crossplane

Форк
0
258 строк · 7.4 Кб
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
	"bytes"
21
	"context"
22
	"io"
23
	"os"
24
	"strings"
25

26
	v1 "github.com/google/go-containerregistry/pkg/v1"
27
	"github.com/google/go-containerregistry/pkg/v1/empty"
28
	"github.com/google/go-containerregistry/pkg/v1/mutate"
29
	"k8s.io/apimachinery/pkg/runtime"
30
	"k8s.io/apimachinery/pkg/runtime/serializer/json"
31

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

35
	pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
36
	"github.com/crossplane/crossplane/apis/pkg/meta/v1beta1"
37
	"github.com/crossplane/crossplane/internal/xpkg/parser/examples"
38
)
39

40
const (
41
	errParserPackage     = "failed to parse package"
42
	errParserExample     = "failed to parse examples"
43
	errLintPackage       = "failed to lint package"
44
	errInitBackend       = "failed to initialize package parsing backend"
45
	errTarFromStream     = "failed to build tarball from stream"
46
	errLayerFromTar      = "failed to convert tarball to image layer"
47
	errDigestInvalid     = "failed to get digest from image layer"
48
	errBuildImage        = "failed to build image from layers"
49
	errConfigFile        = "failed to get config file from image"
50
	errMutateConfig      = "failed to mutate config for image"
51
	errBuildObjectScheme = "failed to build scheme for package encoder"
52
)
53

54
// annotatedTeeReadCloser is a copy of io.TeeReader that implements
55
// parser.AnnotatedReadCloser. It returns a Reader that writes to w what it
56
// reads from r. All reads from r performed through it are matched with
57
// corresponding writes to w. There is no internal buffering - the write must
58
// complete before the read completes. Any error encountered while writing is
59
// reported as a read error. If the underlying reader is a
60
// parser.AnnotatedReadCloser the tee reader will invoke its Annotate function.
61
// Otherwise it will return nil. Closing is always a no-op.
62
func annotatedTeeReadCloser(r io.Reader, w io.Writer) *teeReader {
63
	return &teeReader{r, w}
64
}
65

66
type teeReader struct {
67
	r io.Reader
68
	w io.Writer
69
}
70

71
func (t *teeReader) Read(p []byte) (n int, err error) {
72
	n, err = t.r.Read(p)
73
	if n > 0 {
74
		if n, err := t.w.Write(p[:n]); err != nil {
75
			return n, err
76
		}
77
	}
78
	return
79
}
80

81
func (t *teeReader) Close() error {
82
	return nil
83
}
84

85
func (t *teeReader) Annotate() any {
86
	anno, ok := t.r.(parser.AnnotatedReadCloser)
87
	if !ok {
88
		return nil
89
	}
90
	return anno.Annotate()
91
}
92

93
// Builder defines an xpkg Builder.
94
type Builder struct {
95
	packageSource parser.Backend
96
	exampleSource parser.Backend
97

98
	packageParser  parser.Parser
99
	examplesParser *examples.Parser
100
}
101

102
// New returns a new Builder.
103
func New(packageSource, exampleSource parser.Backend, packageParser parser.Parser, examplesParser *examples.Parser) *Builder {
104
	return &Builder{
105
		packageSource:  packageSource,
106
		exampleSource:  exampleSource,
107
		packageParser:  packageParser,
108
		examplesParser: examplesParser,
109
	}
110
}
111

112
type buildOpts struct {
113
	base v1.Image
114
}
115

116
// A BuildOpt modifies how a package is built.
117
type BuildOpt func(*buildOpts)
118

119
// WithBase sets the base image of the package.
120
func WithBase(img v1.Image) BuildOpt {
121
	return func(o *buildOpts) {
122
		o.base = img
123
	}
124
}
125

126
// Build compiles a Crossplane package from an on-disk package.
127
func (b *Builder) Build(ctx context.Context, opts ...BuildOpt) (v1.Image, runtime.Object, error) { //nolint:gocyclo // TODO(lsviben) consider refactoring
128
	bOpts := &buildOpts{
129
		base: empty.Image,
130
	}
131
	for _, o := range opts {
132
		o(bOpts)
133
	}
134

135
	// assume examples exist
136
	examplesExist := true
137
	// Get package YAML stream.
138
	pkgReader, err := b.packageSource.Init(ctx)
139
	if err != nil {
140
		return nil, nil, errors.Wrap(err, errInitBackend)
141
	}
142
	defer func() { _ = pkgReader.Close() }()
143

144
	// Get examples YAML stream.
145
	exReader, err := b.exampleSource.Init(ctx)
146
	if err != nil && !os.IsNotExist(err) {
147
		return nil, nil, errors.Wrap(err, errInitBackend)
148
	}
149
	defer func() { _ = exReader.Close() }()
150
	// examples/ doesn't exist
151
	if os.IsNotExist(err) {
152
		examplesExist = false
153
	}
154

155
	pkg, err := b.packageParser.Parse(ctx, pkgReader)
156
	if err != nil {
157
		return nil, nil, errors.Wrap(err, errParserPackage)
158
	}
159

160
	metas := pkg.GetMeta()
161
	if len(metas) != 1 {
162
		return nil, nil, errors.New(errNotExactlyOneMeta)
163
	}
164

165
	// TODO(hasheddan): make linter selection logic configurable.
166
	meta := metas[0]
167
	var linter parser.Linter
168
	switch meta.GetObjectKind().GroupVersionKind().Kind {
169
	case pkgmetav1.ConfigurationKind:
170
		linter = NewConfigurationLinter()
171
	case v1beta1.FunctionKind:
172
		linter = NewFunctionLinter()
173
	case pkgmetav1.ProviderKind:
174
		linter = NewProviderLinter()
175
	}
176
	if err := linter.Lint(pkg); err != nil {
177
		return nil, nil, errors.Wrap(err, errLintPackage)
178
	}
179

180
	layers := make([]v1.Layer, 0)
181
	cfgFile, err := bOpts.base.ConfigFile()
182
	if err != nil {
183
		return nil, nil, errors.Wrap(err, errConfigFile)
184
	}
185

186
	cfg := cfgFile.Config
187
	cfg.Labels = make(map[string]string)
188

189
	pkgBytes, err := encode(pkg)
190
	if err != nil {
191
		return nil, nil, errors.Wrap(err, errConfigFile)
192
	}
193

194
	pkgLayer, err := Layer(pkgBytes, StreamFile, PackageAnnotation, int64(pkgBytes.Len()), StreamFileMode, &cfg)
195
	if err != nil {
196
		return nil, nil, err
197
	}
198
	layers = append(layers, pkgLayer)
199

200
	// examples exist, create the layer
201
	if examplesExist {
202
		exBuf := new(bytes.Buffer)
203
		if _, err = b.examplesParser.Parse(ctx, annotatedTeeReadCloser(exReader, exBuf)); err != nil {
204
			return nil, nil, errors.Wrap(err, errParserExample)
205
		}
206

207
		exLayer, err := Layer(exBuf, XpkgExamplesFile, ExamplesAnnotation, int64(exBuf.Len()), StreamFileMode, &cfg)
208
		if err != nil {
209
			return nil, nil, err
210
		}
211
		layers = append(layers, exLayer)
212
	}
213

214
	for _, l := range layers {
215
		bOpts.base, err = mutate.AppendLayers(bOpts.base, l)
216
		if err != nil {
217
			return nil, nil, errors.Wrap(err, errBuildImage)
218
		}
219
	}
220

221
	bOpts.base, err = mutate.Config(bOpts.base, cfg)
222
	if err != nil {
223
		return nil, nil, errors.Wrap(err, errMutateConfig)
224
	}
225

226
	return bOpts.base, meta, nil
227
}
228

229
// encode encodes a package as a YAML stream.  Does not check meta existence
230
// or quantity i.e. it should be linted first to ensure that it is valid.
231
func encode(pkg parser.Lintable) (*bytes.Buffer, error) {
232
	pkgBuf := new(bytes.Buffer)
233
	objScheme, err := BuildObjectScheme()
234
	if err != nil {
235
		return nil, errors.New(errBuildObjectScheme)
236
	}
237

238
	do := json.NewSerializerWithOptions(json.DefaultMetaFactory, objScheme, objScheme, json.SerializerOptions{Yaml: true})
239
	pkgBuf.WriteString("---\n")
240
	if err = do.Encode(pkg.GetMeta()[0], pkgBuf); err != nil {
241
		return nil, errors.Wrap(err, errBuildObjectScheme)
242
	}
243
	pkgBuf.WriteString("---\n")
244
	for _, o := range pkg.GetObjects() {
245
		if err = do.Encode(o, pkgBuf); err != nil {
246
			return nil, errors.Wrap(err, errBuildObjectScheme)
247
		}
248
		pkgBuf.WriteString("---\n")
249
	}
250
	return pkgBuf, nil
251
}
252

253
// SkipContains supplies a FilterFn that skips paths that contain the give pattern.
254
func SkipContains(pattern string) parser.FilterFn {
255
	return func(path string, _ os.FileInfo) (bool, error) {
256
		return strings.Contains(path, pattern), nil
257
	}
258
}
259

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

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

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

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