crossplane
363 строки · 9.4 Кб
1/*
2Copyright 2023 The Crossplane Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package xpkg
18
19import (
20"archive/tar"
21"context"
22"io"
23"os"
24"sort"
25"testing"
26
27"github.com/google/go-cmp/cmp"
28"github.com/google/go-cmp/cmp/cmpopts"
29v1 "github.com/google/go-containerregistry/pkg/v1"
30"github.com/google/go-containerregistry/pkg/v1/mutate"
31"github.com/google/go-containerregistry/pkg/v1/partial"
32"github.com/spf13/afero"
33"github.com/spf13/afero/tarfs"
34
35"github.com/crossplane/crossplane-runtime/pkg/errors"
36"github.com/crossplane/crossplane-runtime/pkg/parser"
37"github.com/crossplane/crossplane-runtime/pkg/test"
38
39"github.com/crossplane/crossplane/internal/xpkg/parser/examples"
40)
41
42var (
43testCRD []byte
44testMeta []byte
45testEx1 []byte
46testEx2 []byte
47testEx3 []byte
48testEx4 []byte
49
50_ parser.Backend = &MockBackend{}
51)
52
53func init() {
54testCRD, _ = afero.ReadFile(afero.NewOsFs(), "testdata/providerconfigs.helm.crossplane.io.yaml")
55testMeta, _ = afero.ReadFile(afero.NewOsFs(), "testdata/provider_meta.yaml")
56testEx1, _ = afero.ReadFile(afero.NewOsFs(), "testdata/examples/ec2/instance.yaml")
57testEx2, _ = afero.ReadFile(afero.NewOsFs(), "testdata/examples/ec2/internetgateway.yaml")
58testEx3, _ = afero.ReadFile(afero.NewOsFs(), "testdata/examples/ecr/repository.yaml")
59testEx4, _ = afero.ReadFile(afero.NewOsFs(), "testdata/examples/provider.yaml")
60}
61
62type MockBackend struct {
63MockInit func() (io.ReadCloser, error)
64}
65
66func NewMockInitFn(r io.ReadCloser, err error) func() (io.ReadCloser, error) {
67return func() (io.ReadCloser, error) { return r, err }
68}
69
70func (m *MockBackend) Init(_ context.Context, _ ...parser.BackendOption) (io.ReadCloser, error) {
71return m.MockInit()
72}
73
74var _ parser.Parser = &MockParser{}
75
76type MockParser struct {
77MockParse func() (*parser.Package, error)
78}
79
80func NewMockParseFn(pkg *parser.Package, err error) func() (*parser.Package, error) {
81return func() (*parser.Package, error) { return pkg, err }
82}
83
84func (m *MockParser) Parse(context.Context, io.ReadCloser) (*parser.Package, error) {
85return m.MockParse()
86}
87
88func TestBuild(t *testing.T) {
89errBoom := errors.New("boom")
90
91type args struct {
92be parser.Backend
93ex parser.Backend
94p parser.Parser
95e *examples.Parser
96}
97cases := map[string]struct {
98reason string
99args args
100want error
101}{
102"ErrInitBackend": {
103reason: "Should return an error if we fail to initialize backend.",
104args: args{
105be: &MockBackend{
106MockInit: NewMockInitFn(nil, errBoom),
107},
108},
109want: errors.Wrap(errBoom, errInitBackend),
110},
111"ErrParse": {
112reason: "Should return an error if we fail to parse package.",
113args: args{
114be: parser.NewEchoBackend(""),
115ex: parser.NewEchoBackend(""),
116p: &MockParser{
117MockParse: NewMockParseFn(nil, errBoom),
118},
119},
120want: errors.Wrap(errBoom, errParserPackage),
121},
122}
123
124for name, tc := range cases {
125t.Run(name, func(t *testing.T) {
126builder := New(tc.args.be, tc.args.ex, tc.args.p, tc.args.e)
127
128_, _, err := builder.Build(context.TODO())
129
130if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" {
131t.Errorf("\n%s\nBuild(...): -want err, +got err:\n%s", tc.reason, diff)
132}
133})
134}
135}
136
137func TestBuildExamples(t *testing.T) {
138pkgp, _ := yamlParser()
139
140defaultFilters := []parser.FilterFn{
141parser.SkipDirs(),
142parser.SkipNotYAML(),
143parser.SkipEmpty(),
144}
145
146type withFsFn func() afero.Fs
147
148type args struct {
149rootDir string
150examplesDir string
151fs withFsFn
152}
153type want struct {
154pkgExists bool
155exExists bool
156labels []string
157err error
158}
159
160cases := map[string]struct {
161reason string
162args args
163want want
164}{
165"SuccessNoExamples": {
166args: args{
167rootDir: "/ws",
168examplesDir: "/ws/examples",
169fs: func() afero.Fs {
170fs := afero.NewMemMapFs()
171_ = fs.Mkdir("/ws", os.ModePerm)
172_ = fs.Mkdir("/ws/crds", os.ModePerm)
173_ = afero.WriteFile(fs, "/ws/crossplane.yaml", testMeta, os.ModePerm)
174_ = afero.WriteFile(fs, "/ws/crds/crd.yaml", testCRD, os.ModePerm)
175return fs
176},
177},
178want: want{
179pkgExists: true,
180labels: []string{
181PackageAnnotation,
182},
183},
184},
185"SuccessExamplesAtRoot": {
186args: args{
187rootDir: "/ws",
188examplesDir: "/ws/examples",
189fs: func() afero.Fs {
190fs := afero.NewMemMapFs()
191_ = fs.Mkdir("/ws", os.ModePerm)
192_ = afero.WriteFile(fs, "/ws/crossplane.yaml", testMeta, os.ModePerm)
193_ = afero.WriteFile(fs, "/ws/crds/crd.yaml", testCRD, os.ModePerm)
194_ = afero.WriteFile(fs, "/ws/examples/ec2/instance.yaml", testEx1, os.ModePerm)
195_ = afero.WriteFile(fs, "/ws/examples/ec2/internetgateway.yaml", testEx2, os.ModePerm)
196_ = afero.WriteFile(fs, "/ws/examples/ecr/repository.yaml", testEx3, os.ModePerm)
197_ = afero.WriteFile(fs, "/ws/examples/provider.yaml", testEx4, os.ModePerm)
198return fs
199},
200},
201want: want{
202pkgExists: true,
203exExists: true,
204labels: []string{
205PackageAnnotation,
206ExamplesAnnotation,
207},
208},
209},
210"SuccessExamplesAtCustomDir": {
211args: args{
212rootDir: "/ws",
213examplesDir: "/other_directory/examples",
214fs: func() afero.Fs {
215fs := afero.NewMemMapFs()
216_ = fs.Mkdir("/ws", os.ModePerm)
217_ = fs.Mkdir("/other_directory", os.ModePerm)
218_ = afero.WriteFile(fs, "/ws/crossplane.yaml", testMeta, os.ModePerm)
219_ = afero.WriteFile(fs, "/ws/crds/crd.yaml", testCRD, os.ModePerm)
220_ = afero.WriteFile(fs, "/other_directory/examples/ec2/instance.yaml", testEx1, os.ModePerm)
221_ = afero.WriteFile(fs, "/other_directory/examples/ec2/internetgateway.yaml", testEx2, os.ModePerm)
222_ = afero.WriteFile(fs, "/other_directory/examples/ecr/repository.yaml", testEx3, os.ModePerm)
223_ = afero.WriteFile(fs, "/other_directory/examples/provider.yaml", testEx4, os.ModePerm)
224return fs
225},
226},
227want: want{
228pkgExists: true,
229exExists: true,
230labels: []string{
231PackageAnnotation,
232ExamplesAnnotation,
233},
234},
235},
236}
237
238for name, tc := range cases {
239t.Run(name, func(t *testing.T) {
240pkgBe := parser.NewFsBackend(
241tc.args.fs(),
242parser.FsDir(tc.args.rootDir),
243parser.FsFilters([]parser.FilterFn{
244parser.SkipDirs(),
245parser.SkipNotYAML(),
246parser.SkipEmpty(),
247SkipContains("examples/"), // don't try to parse the examples in the package
248}...),
249)
250pkgEx := parser.NewFsBackend(
251tc.args.fs(),
252parser.FsDir(tc.args.examplesDir),
253parser.FsFilters(defaultFilters...),
254)
255
256builder := New(pkgBe, pkgEx, pkgp, examples.New())
257
258img, _, err := builder.Build(context.TODO())
259
260if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
261t.Errorf("\n%s\nBuildExamples(...): -want err, +got err:\n%s", tc.reason, diff)
262}
263
264// validate the xpkg img has the correct annotations, etc
265contents, err := readImg(img)
266// sort the contents slice for test comparison
267sort.Strings(contents.labels)
268
269if diff := cmp.Diff(tc.want.pkgExists, len(contents.pkgBytes) != 0); diff != "" {
270t.Errorf("\n%s\nBuildExamples(...): -want err, +got err:\n%s", tc.reason, diff)
271}
272if diff := cmp.Diff(tc.want.exExists, len(contents.exBytes) != 0); diff != "" {
273t.Errorf("\n%s\nBuildExamples(...): -want err, +got err:\n%s", tc.reason, diff)
274}
275if diff := cmp.Diff(tc.want.labels, contents.labels, cmpopts.SortSlices(func(i, j int) bool {
276return contents.labels[i] < contents.labels[j]
277})); diff != "" {
278t.Errorf("\n%s\nBuildExamples(...): -want err, +got err:\n%s", tc.reason, diff)
279}
280if diff := cmp.Diff(nil, err, test.EquateErrors()); diff != "" {
281t.Errorf("\n%s\nBuildExamples(...): -want err, +got err:\n%s", tc.reason, diff)
282}
283})
284}
285}
286
287type xpkgContents struct {
288labels []string
289pkgBytes []byte
290exBytes []byte
291}
292
293func readImg(i v1.Image) (xpkgContents, error) {
294contents := xpkgContents{
295labels: make([]string, 0),
296}
297
298reader := mutate.Extract(i)
299fs := tarfs.New(tar.NewReader(reader))
300pkgYaml, err := fs.Open(StreamFile)
301if err != nil {
302return contents, err
303}
304
305pkgBytes, err := io.ReadAll(pkgYaml)
306if err != nil {
307return contents, err
308}
309contents.pkgBytes = pkgBytes
310
311exYaml, err := fs.Open(XpkgExamplesFile)
312if err != nil && !os.IsNotExist(err) {
313return contents, err
314}
315
316if exYaml != nil {
317exBytes, err := io.ReadAll(exYaml)
318if err != nil {
319return contents, err
320}
321contents.exBytes = exBytes
322}
323
324labels, err := allLabels(i)
325if err != nil {
326return contents, err
327}
328
329contents.labels = labels
330
331return contents, nil
332}
333
334func allLabels(i partial.WithConfigFile) ([]string, error) {
335labels := []string{}
336
337cfgFile, err := i.ConfigFile()
338if err != nil {
339return labels, err
340}
341
342cfg := cfgFile.Config
343
344for _, label := range cfg.Labels {
345labels = append(labels, label)
346}
347
348return labels, nil
349}
350
351// This is equivalent to yaml.New. Duplicated here to avoid an import cycle.
352func yamlParser() (*parser.PackageParser, error) {
353metaScheme, err := BuildMetaScheme()
354if err != nil {
355panic(err)
356}
357objScheme, err := BuildObjectScheme()
358if err != nil {
359panic(err)
360}
361
362return parser.New(metaScheme, objScheme), nil
363}
364