diff --git a/alpha/template/basic/basic.go b/alpha/template/basic/basic.go deleted file mode 100644 index 18566fbcf..000000000 --- a/alpha/template/basic/basic.go +++ /dev/null @@ -1,50 +0,0 @@ -package basic - -import ( - "context" - "fmt" - "io" - - "github.com/operator-framework/operator-registry/alpha/action" - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/pkg/image" -) - -type Template struct { - Registry image.Registry -} - -func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { - cfg, err := declcfg.LoadReader(reader) - if err != nil { - return cfg, err - } - - outb := cfg.Bundles[:0] // allocate based on max size of input, but empty slice - // populate registry, incl any flags from CLI, and enforce only rendering bundle images - r := action.Render{ - Registry: t.Registry, - AllowedRefMask: action.RefBundleImage, - } - - for _, b := range cfg.Bundles { - if !isBundleTemplate(&b) { - return nil, fmt.Errorf("unexpected fields present in basic template bundle") - } - r.Refs = []string{b.Image} - contributor, err := r.Run(ctx) - if err != nil { - return nil, err - } - outb = append(outb, contributor.Bundles...) - } - - cfg.Bundles = outb - return cfg, nil -} - -// isBundleTemplate identifies a Bundle template source as having a Schema and Image defined -// but no Properties, RelatedImages or Package defined -func isBundleTemplate(b *declcfg.Bundle) bool { - return b.Schema != "" && b.Image != "" && b.Package == "" && len(b.Properties) == 0 && len(b.RelatedImages) == 0 -} diff --git a/alpha/template/composite/builder.go b/alpha/template/composite/builder.go deleted file mode 100644 index e22b5e49c..000000000 --- a/alpha/template/composite/builder.go +++ /dev/null @@ -1,363 +0,0 @@ -package composite - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - basictemplate "github.com/operator-framework/operator-registry/alpha/template/basic" - semvertemplate "github.com/operator-framework/operator-registry/alpha/template/semver" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/lib/config" -) - -const ( - BasicBuilderSchema = "olm.builder.basic" - SemverBuilderSchema = "olm.builder.semver" - RawBuilderSchema = "olm.builder.raw" - CustomBuilderSchema = "olm.builder.custom" -) - -type BuilderConfig struct { - WorkingDir string - OutputType string - ContributionPath string -} - -type Builder interface { - Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error - Validate(ctx context.Context, dir string) error -} - -type BasicBuilder struct { - builderCfg BuilderConfig -} - -var _ Builder = &BasicBuilder{} - -func NewBasicBuilder(builderCfg BuilderConfig) *BasicBuilder { - return &BasicBuilder{ - builderCfg: builderCfg, - } -} - -func (bb *BasicBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { - if td.Schema != BasicBuilderSchema { - return fmt.Errorf("schema %q does not match the basic template builder schema %q", td.Schema, BasicBuilderSchema) - } - // Parse out the basic template configuration - basicConfig := &BasicConfig{} - err := yaml.UnmarshalStrict(td.Config, basicConfig) - if err != nil { - return fmt.Errorf("unmarshalling basic template config: %w", err) - } - - // validate the basic config fields - valid := true - validationErrs := []string{} - if basicConfig.Input == "" { - valid = false - validationErrs = append(validationErrs, "basic template config must have a non-empty input (templateDefinition.config.input)") - } - - if basicConfig.Output == "" { - valid = false - validationErrs = append(validationErrs, "basic template config must have a non-empty output (templateDefinition.config.output)") - } - - if !valid { - return fmt.Errorf("basic template configuration is invalid: %s", strings.Join(validationErrs, ",")) - } - - b := basictemplate.Template{Registry: reg} - reader, err := os.Open(basicConfig.Input) - if err != nil { - if os.IsNotExist(err) && bb.builderCfg.ContributionPath != "" { - reader, err = os.Open(path.Join(bb.builderCfg.ContributionPath, basicConfig.Input)) - if err != nil { - return fmt.Errorf("error reading basic template: %v (tried contribution-local path: %q)", err, bb.builderCfg.ContributionPath) - } - } else { - return fmt.Errorf("error reading basic template: %v", err) - } - } - defer reader.Close() - - dcfg, err := b.Render(ctx, reader) - if err != nil { - return fmt.Errorf("error rendering basic template: %v", err) - } - - destPath := path.Join(bb.builderCfg.WorkingDir, dir, basicConfig.Output) - - return build(dcfg, destPath, bb.builderCfg.OutputType) -} - -func (bb *BasicBuilder) Validate(ctx context.Context, dir string) error { - return validate(ctx, bb.builderCfg, dir) -} - -type SemverBuilder struct { - builderCfg BuilderConfig -} - -var _ Builder = &SemverBuilder{} - -func NewSemverBuilder(builderCfg BuilderConfig) *SemverBuilder { - return &SemverBuilder{ - builderCfg: builderCfg, - } -} - -func (sb *SemverBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { - if td.Schema != SemverBuilderSchema { - return fmt.Errorf("schema %q does not match the semver template builder schema %q", td.Schema, SemverBuilderSchema) - } - // Parse out the semver template configuration - semverConfig := &SemverConfig{} - err := yaml.UnmarshalStrict(td.Config, semverConfig) - if err != nil { - return fmt.Errorf("unmarshalling semver template config: %w", err) - } - - // validate the semver config fields - valid := true - validationErrs := []string{} - if semverConfig.Input == "" { - valid = false - validationErrs = append(validationErrs, "semver template config must have a non-empty input (templateDefinition.config.input)") - } - - if semverConfig.Output == "" { - valid = false - validationErrs = append(validationErrs, "semver template config must have a non-empty output (templateDefinition.config.output)") - } - - if !valid { - return fmt.Errorf("semver template configuration is invalid: %s", strings.Join(validationErrs, ",")) - } - - reader, err := os.Open(semverConfig.Input) - if err != nil { - if os.IsNotExist(err) && sb.builderCfg.ContributionPath != "" { - reader, err = os.Open(path.Join(sb.builderCfg.ContributionPath, semverConfig.Input)) - if err != nil { - return fmt.Errorf("error reading semver template: %v (tried contribution-local path: %q)", err, sb.builderCfg.ContributionPath) - } - } else { - return fmt.Errorf("error reading semver template: %v", err) - } - } - defer reader.Close() - - s := semvertemplate.Template{Registry: reg, Data: reader} - - dcfg, err := s.Render(ctx) - if err != nil { - return fmt.Errorf("error rendering semver template: %v", err) - } - - destPath := path.Join(sb.builderCfg.WorkingDir, dir, semverConfig.Output) - - return build(dcfg, destPath, sb.builderCfg.OutputType) -} - -func (sb *SemverBuilder) Validate(ctx context.Context, dir string) error { - return validate(ctx, sb.builderCfg, dir) -} - -type RawBuilder struct { - builderCfg BuilderConfig -} - -var _ Builder = &RawBuilder{} - -func NewRawBuilder(builderCfg BuilderConfig) *RawBuilder { - return &RawBuilder{ - builderCfg: builderCfg, - } -} - -func (rb *RawBuilder) Build(ctx context.Context, _ image.Registry, dir string, td TemplateDefinition) error { - if td.Schema != RawBuilderSchema { - return fmt.Errorf("schema %q does not match the raw template builder schema %q", td.Schema, RawBuilderSchema) - } - // Parse out the raw template configuration - rawConfig := &RawConfig{} - err := yaml.UnmarshalStrict(td.Config, rawConfig) - if err != nil { - return fmt.Errorf("unmarshalling raw template config: %w", err) - } - - // validate the raw config fields - valid := true - validationErrs := []string{} - if rawConfig.Input == "" { - valid = false - validationErrs = append(validationErrs, "raw template config must have a non-empty input (templateDefinition.config.input)") - } - - if rawConfig.Output == "" { - valid = false - validationErrs = append(validationErrs, "raw template config must have a non-empty output (templateDefinition.config.output)") - } - - if !valid { - return fmt.Errorf("raw template configuration is invalid: %s", strings.Join(validationErrs, ",")) - } - - reader, err := os.Open(rawConfig.Input) - if err != nil { - if os.IsNotExist(err) && rb.builderCfg.ContributionPath != "" { - reader, err = os.Open(path.Join(rb.builderCfg.ContributionPath, rawConfig.Input)) - if err != nil { - return fmt.Errorf("error reading raw input file: %v (tried contribution-local path: %q)", err, rb.builderCfg.ContributionPath) - } - } else { - return fmt.Errorf("error reading raw input file: %v", err) - } - } - defer reader.Close() - - dcfg, err := declcfg.LoadReader(reader) - if err != nil { - return fmt.Errorf("error parsing raw input file: %s, %v", rawConfig.Input, err) - } - - destPath := path.Join(rb.builderCfg.WorkingDir, dir, rawConfig.Output) - - return build(dcfg, destPath, rb.builderCfg.OutputType) -} - -func (rb *RawBuilder) Validate(ctx context.Context, dir string) error { - return validate(ctx, rb.builderCfg, dir) -} - -type CustomBuilder struct { - builderCfg BuilderConfig -} - -var _ Builder = &CustomBuilder{} - -func NewCustomBuilder(builderCfg BuilderConfig) *CustomBuilder { - return &CustomBuilder{ - builderCfg: builderCfg, - } -} - -func (cb *CustomBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { - if td.Schema != CustomBuilderSchema { - return fmt.Errorf("schema %q does not match the custom template builder schema %q", td.Schema, CustomBuilderSchema) - } - // Parse out the raw template configuration - customConfig := &CustomConfig{} - err := yaml.UnmarshalStrict(td.Config, customConfig) - if err != nil { - return fmt.Errorf("unmarshalling custom template config: %w", err) - } - - // validate the custom config fields - valid := true - validationErrs := []string{} - if customConfig.Command == "" { - valid = false - validationErrs = append(validationErrs, "custom template config must have a non-empty command (templateDefinition.config.command)") - } - - if customConfig.Output == "" { - valid = false - validationErrs = append(validationErrs, "custom template config must have a non-empty output (templateDefinition.config.output)") - } - - if !valid { - return fmt.Errorf("custom template configuration is invalid: %s", strings.Join(validationErrs, ",")) - } - // build the command to execute - cmd := exec.Command(customConfig.Command, customConfig.Args...) - - // custom template should output a valid FBC to STDOUT so we can - // build the FBC just like all the other templates. - v, err := cmd.Output() - if err != nil { - return fmt.Errorf("running command %q: %v: %v", cmd.String(), err, v) - } - - reader := bytes.NewReader(v) - - dcfg, err := declcfg.LoadReader(reader) - cmdString := []string{customConfig.Command} - cmdString = append(cmdString, customConfig.Args...) - if err != nil { - return fmt.Errorf("error parsing custom command output: %s, %v", strings.Join(cmdString, "'"), err) - } - - destPath := path.Join(cb.builderCfg.WorkingDir, dir, customConfig.Output) - - // custom template should output a valid FBC to STDOUT so we can - // build the FBC just like all the other templates. - return build(dcfg, destPath, cb.builderCfg.OutputType) -} - -func (cb *CustomBuilder) Validate(ctx context.Context, dir string) error { - return validate(ctx, cb.builderCfg, dir) -} - -func writeDeclCfg(dcfg declcfg.DeclarativeConfig, w io.Writer, output string) error { - switch output { - case "yaml": - return declcfg.WriteYAML(dcfg, w) - case "json": - return declcfg.WriteJSON(dcfg, w) - default: - return fmt.Errorf("invalid --output value %q, expected (json|yaml)", output) - } -} - -func validate(ctx context.Context, builderCfg BuilderConfig, dir string) error { - - path := path.Join(builderCfg.WorkingDir, dir) - s, err := os.Stat(path) - if err != nil { - return fmt.Errorf("directory not found. validation path needs to be composed of BuilderConfig.WorkingDir+Component[].Destination.Path: %q: %v", path, err) - } - if !s.IsDir() { - return fmt.Errorf("%q is not a directory", path) - } - - if err := config.Validate(ctx, os.DirFS(path)); err != nil { - return fmt.Errorf("validation failure in path %q: %v", path, err) - } - return nil -} - -func build(dcfg *declcfg.DeclarativeConfig, outPath string, outType string) error { - // create the destination for output, if it does not exist - outDir := filepath.Dir(outPath) - err := os.MkdirAll(outDir, 0o777) - if err != nil { - return fmt.Errorf("creating output directory %q: %v", outPath, err) - } - - // write the dcfg - file, err := os.Create(outPath) - if err != nil { - return fmt.Errorf("creating output file %q: %v", outPath, err) - } - defer file.Close() - - err = writeDeclCfg(*dcfg, file, outType) - if err != nil { - return fmt.Errorf("writing to output file %q: %v", outPath, err) - } - - return nil -} diff --git a/alpha/template/composite/builder_test.go b/alpha/template/composite/builder_test.go deleted file mode 100644 index 4a70c3067..000000000 --- a/alpha/template/composite/builder_test.go +++ /dev/null @@ -1,1862 +0,0 @@ -package composite - -import ( - "context" - "fmt" - "io" - "os" - "path" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" -) - -// TODO: Should we consolidate all these tests into a singular test function? -// It was intentional to keep them separate for now, but it would significantly reduce code replication to combine into one function -func TestBasicBuilder(t *testing.T) { - type testCase struct { - name string - validate bool - basicBuilder *BasicBuilder - templateDefinition TemplateDefinition - files map[string]string - buildAssertions func(t *testing.T, dir string, buildErr error) - validateAssertions func(t *testing.T, validateErr error) - } - - testDir := t.TempDir() - validConfigTemplate := `{ - "input": "%s", - "output": "%s" - }` - - testCases := []testCase{ - { - name: "successful basic build yaml output", - validate: true, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/basic.yaml": basicYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.yaml") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), basicBuiltFbcYaml) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "successful basic build json output", - validate: true, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.json")), - }, - files: map[string]string{ - "components/basic.yaml": basicYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), basicBuiltFbcJson) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "invalid template configuration", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(`{ - "invalid": "components/basic.yaml", - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "unmarshalling basic template config:") - }, - }, - { - name: "invalid output type", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "invalid", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/basic.yaml": basicYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("invalid --output value %q, expected (json|yaml)", "invalid")) - }, - }, - { - name: "invalid schema", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: "olm.invalid", - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("schema %q does not match the basic template builder schema %q", "olm.invalid", BasicBuilderSchema)) - }, - }, - { - name: "template config has empty input", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(`{ - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "basic template configuration is invalid: basic template config must have a non-empty input (templateDefinition.config.input)") - }, - }, - { - name: "template config has empty output", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(`{ - "input": "components/basic.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "basic template configuration is invalid: basic template config must have a non-empty output (templateDefinition.config.output)") - }, - }, - { - name: "template config has empty input & output", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(`{}`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "basic template configuration is invalid: basic template config must have a non-empty input (templateDefinition.config.input),basic template config must have a non-empty output (templateDefinition.config.output)") - }, - }, - { - name: "successful basic build yaml output using contribution cwd to find inputs", - validate: true, - basicBuilder: NewBasicBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - ContributionPath: filepath.Join(testDir, "components"), - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, "basic.yaml", "catalog.yaml")), - }, - files: map[string]string{ - "components/basic.yaml": basicYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.yaml") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), basicBuiltFbcYaml) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - } - - for i, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - outDir := fmt.Sprintf("basic-%d", i) - outPath := path.Join(testDir, outDir) - err := os.MkdirAll(outPath, 0o777) - require.NoError(t, err) - - // create files in temp dir - for fileName, fileContents := range tc.files { - err := os.MkdirAll(path.Join(testDir, path.Dir(fileName)), 0o777) - require.NoError(t, err) - file, err := os.Create(path.Join(testDir, fileName)) - require.NoError(t, err) - _, err = file.WriteString(fileContents) - require.NoError(t, err) - } - - cacheDir, err := os.MkdirTemp("", "opm-registry-") - require.NoError(t, err) - - reg, err := containerdregistry.NewRegistry( - containerdregistry.WithCacheDir(cacheDir), - ) - defer reg.Destroy() - require.NoError(t, err) - - buildErr := tc.basicBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) - tc.buildAssertions(t, outPath, buildErr) - - if tc.validate { - validateErr := tc.basicBuilder.Validate(context.Background(), outDir) - tc.validateAssertions(t, validateErr) - } - }) - } -} - -const basicYaml = `--- -defaultChannel: preview -name: webhook-operator -schema: olm.package ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -schema: olm.bundle ---- -schema: olm.channel -package: webhook-operator -name: preview -entries: - - name: webhook-operator.v0.0.1 -` - -const basicBuiltFbcYaml = `--- -defaultChannel: preview -name: webhook-operator -schema: olm.package ---- -entries: -- name: webhook-operator.v0.0.1 -name: preview -package: webhook-operator -schema: olm.channel ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator - version: 0.0.1 -- type: olm.csv.metadata - value: - annotations: - alm-examples: |- - [ - { - "apiVersion": "webhook.operators.coreos.io/v1", - "kind": "WebhookTest", - "metadata": { - "name": "webhooktest-sample", - "namespace": "webhook-operator-system" - }, - "spec": { - "valid": true - } - } - ] - capabilities: Basic Install - operators.operatorframework.io/builder: operator-sdk-v1.0.0 - operators.operatorframework.io/project_layout: go - apiServiceDefinitions: {} - crdDescriptions: - owned: - - kind: WebhookTest - name: webhooktests.webhook.operators.coreos.io - version: v1 - description: Webhook Operator description. TODO. - displayName: Webhook Operator - installModes: - - supported: false - type: OwnNamespace - - supported: false - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: true - type: AllNamespaces - keywords: - - webhook-operator - links: - - name: Webhook Operator - url: https://webhook-operator.domain - maintainers: - - email: your@email.com - name: Maintainer Name - maturity: alpha - provider: - name: Provider Name - url: https://your.domain -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle -` - -const basicBuiltFbcJson = `{ - "schema": "olm.package", - "name": "webhook-operator", - "defaultChannel": "preview" -} -{ - "schema": "olm.channel", - "name": "preview", - "package": "webhook-operator", - "entries": [ - { - "name": "webhook-operator.v0.0.1" - } - ] -} -{ - "schema": "olm.bundle", - "name": "webhook-operator.v0.0.1", - "package": "webhook-operator", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3", - "properties": [ - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v1" - } - }, - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v2" - } - }, - { - "type": "olm.package", - "value": { - "packageName": "webhook-operator", - "version": "0.0.1" - } - }, - { - "type": "olm.csv.metadata", - "value": { - "annotations": { - "alm-examples": "[\n {\n \"apiVersion\": \"webhook.operators.coreos.io/v1\",\n \"kind\": \"WebhookTest\",\n \"metadata\": {\n \"name\": \"webhooktest-sample\",\n \"namespace\": \"webhook-operator-system\"\n },\n \"spec\": {\n \"valid\": true\n }\n }\n]", - "capabilities": "Basic Install", - "operators.operatorframework.io/builder": "operator-sdk-v1.0.0", - "operators.operatorframework.io/project_layout": "go" - }, - "apiServiceDefinitions": {}, - "crdDescriptions": { - "owned": [ - { - "name": "webhooktests.webhook.operators.coreos.io", - "version": "v1", - "kind": "WebhookTest" - } - ] - }, - "description": "Webhook Operator description. TODO.", - "displayName": "Webhook Operator", - "installModes": [ - { - "type": "OwnNamespace", - "supported": false - }, - { - "type": "SingleNamespace", - "supported": false - }, - { - "type": "MultiNamespace", - "supported": false - }, - { - "type": "AllNamespaces", - "supported": true - } - ], - "keywords": [ - "webhook-operator" - ], - "links": [ - { - "name": "Webhook Operator", - "url": "https://webhook-operator.domain" - } - ], - "maintainers": [ - { - "name": "Maintainer Name", - "email": "your@email.com" - } - ], - "maturity": "alpha", - "provider": { - "name": "Provider Name", - "url": "https://your.domain" - } - } - } - ], - "relatedImages": [ - { - "name": "", - "image": "gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator:0.0.3" - } - ] -} -` - -func TestSemverBuilder(t *testing.T) { - type testCase struct { - name string - validate bool - semverBuilder *SemverBuilder - templateDefinition TemplateDefinition - files map[string]string - buildAssertions func(t *testing.T, dir string, buildErr error) - validateAssertions func(t *testing.T, validateErr error) - } - - testDir := t.TempDir() - validConfigTemplate := `{ - "input": "%s", - "output": "%s" - }` - - testCases := []testCase{ - { - name: "successful semver build yaml output", - validate: true, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/semver.yaml": semverYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.yaml") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, semverBuiltFbcYaml, string(fileData)) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "successful semver build json output", - validate: true, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.json")), - }, - files: map[string]string{ - "components/semver.yaml": semverYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, semverBuiltFbcJson, string(fileData)) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "invalid template configuration", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(`{ - "invalid": "components/semver.yaml", - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "unmarshalling semver template config:") - }, - }, - { - name: "invalid output type", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "invalid", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/semver.yaml": semverYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("invalid --output value %q, expected (json|yaml)", "invalid")) - }, - }, - { - name: "invalid schema", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: "olm.invalid", - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("schema %q does not match the semver template builder schema %q", "olm.invalid", SemverBuilderSchema)) - }, - }, - { - name: "template config has empty input", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(`{ - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "semver template configuration is invalid: semver template config must have a non-empty input (templateDefinition.config.input)") - }, - }, - { - name: "template config has empty output", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(`{ - "input": "components/semver.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - "semver template configuration is invalid: semver template config must have a non-empty output (templateDefinition.config.output)", - buildErr.Error()) - }, - }, - { - name: "template config has empty input & output", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(`{}`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - "semver template configuration is invalid: semver template config must have a non-empty input (templateDefinition.config.input),semver template config must have a non-empty output (templateDefinition.config.output)", - buildErr.Error(), - ) - }, - }, - { - name: "successful semver build json output using contribution cwd to find inputs", - validate: true, - semverBuilder: NewSemverBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - ContributionPath: filepath.Join(testDir, "components"), - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, "semver.yaml", "catalog.json")), - }, - files: map[string]string{ - "components/semver.yaml": semverYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, semverBuiltFbcJson, string(fileData)) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - } - - for i, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - outDir := fmt.Sprintf("semver-%d", i) - outPath := path.Join(testDir, outDir) - err := os.MkdirAll(outPath, 0o777) - require.NoError(t, err) - - // create files in temp dir - for fileName, fileContents := range tc.files { - err := os.MkdirAll(path.Join(testDir, path.Dir(fileName)), 0o777) - require.NoError(t, err) - file, err := os.Create(path.Join(testDir, fileName)) - require.NoError(t, err) - _, err = file.WriteString(fileContents) - require.NoError(t, err) - // fmt.Printf("wrote file: %q\n", path.Join(testDir, fileName)) - } - - cacheDir, err := os.MkdirTemp("", "opm-registry-") - require.NoError(t, err) - - reg, err := containerdregistry.NewRegistry( - containerdregistry.WithCacheDir(cacheDir), - ) - defer reg.Destroy() - require.NoError(t, err) - - buildErr := tc.semverBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) - tc.buildAssertions(t, outPath, buildErr) - - if tc.validate { - validateErr := tc.semverBuilder.Validate(context.Background(), outDir) - tc.validateAssertions(t, validateErr) - } - }) - } -} - -const semverYaml = `--- -Schema: olm.semver -GenerateMajorChannels: true -GenerateMinorChannels: true -Stable: - Bundles: - - Image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -` - -const semverBuiltFbcYaml = `--- -defaultChannel: stable-v0.0 -name: webhook-operator -schema: olm.package ---- -entries: -- name: webhook-operator.v0.0.1 -name: stable-v0 -package: webhook-operator -schema: olm.channel ---- -entries: -- name: webhook-operator.v0.0.1 -name: stable-v0.0 -package: webhook-operator -schema: olm.channel ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator - version: 0.0.1 -- type: olm.csv.metadata - value: - annotations: - alm-examples: |- - [ - { - "apiVersion": "webhook.operators.coreos.io/v1", - "kind": "WebhookTest", - "metadata": { - "name": "webhooktest-sample", - "namespace": "webhook-operator-system" - }, - "spec": { - "valid": true - } - } - ] - capabilities: Basic Install - operators.operatorframework.io/builder: operator-sdk-v1.0.0 - operators.operatorframework.io/project_layout: go - apiServiceDefinitions: {} - crdDescriptions: - owned: - - kind: WebhookTest - name: webhooktests.webhook.operators.coreos.io - version: v1 - description: Webhook Operator description. TODO. - displayName: Webhook Operator - installModes: - - supported: false - type: OwnNamespace - - supported: false - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: true - type: AllNamespaces - keywords: - - webhook-operator - links: - - name: Webhook Operator - url: https://webhook-operator.domain - maintainers: - - email: your@email.com - name: Maintainer Name - maturity: alpha - provider: - name: Provider Name - url: https://your.domain -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle -` - -const semverBuiltFbcJson = `{ - "schema": "olm.package", - "name": "webhook-operator", - "defaultChannel": "stable-v0.0" -} -{ - "schema": "olm.channel", - "name": "stable-v0", - "package": "webhook-operator", - "entries": [ - { - "name": "webhook-operator.v0.0.1" - } - ] -} -{ - "schema": "olm.channel", - "name": "stable-v0.0", - "package": "webhook-operator", - "entries": [ - { - "name": "webhook-operator.v0.0.1" - } - ] -} -{ - "schema": "olm.bundle", - "name": "webhook-operator.v0.0.1", - "package": "webhook-operator", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3", - "properties": [ - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v1" - } - }, - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v2" - } - }, - { - "type": "olm.package", - "value": { - "packageName": "webhook-operator", - "version": "0.0.1" - } - }, - { - "type": "olm.csv.metadata", - "value": { - "annotations": { - "alm-examples": "[\n {\n \"apiVersion\": \"webhook.operators.coreos.io/v1\",\n \"kind\": \"WebhookTest\",\n \"metadata\": {\n \"name\": \"webhooktest-sample\",\n \"namespace\": \"webhook-operator-system\"\n },\n \"spec\": {\n \"valid\": true\n }\n }\n]", - "capabilities": "Basic Install", - "operators.operatorframework.io/builder": "operator-sdk-v1.0.0", - "operators.operatorframework.io/project_layout": "go" - }, - "apiServiceDefinitions": {}, - "crdDescriptions": { - "owned": [ - { - "name": "webhooktests.webhook.operators.coreos.io", - "version": "v1", - "kind": "WebhookTest" - } - ] - }, - "description": "Webhook Operator description. TODO.", - "displayName": "Webhook Operator", - "installModes": [ - { - "type": "OwnNamespace", - "supported": false - }, - { - "type": "SingleNamespace", - "supported": false - }, - { - "type": "MultiNamespace", - "supported": false - }, - { - "type": "AllNamespaces", - "supported": true - } - ], - "keywords": [ - "webhook-operator" - ], - "links": [ - { - "name": "Webhook Operator", - "url": "https://webhook-operator.domain" - } - ], - "maintainers": [ - { - "name": "Maintainer Name", - "email": "your@email.com" - } - ], - "maturity": "alpha", - "provider": { - "name": "Provider Name", - "url": "https://your.domain" - } - } - } - ], - "relatedImages": [ - { - "name": "", - "image": "gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator:0.0.3" - } - ] -} -` - -func TestRawBuilder(t *testing.T) { - type testCase struct { - name string - validate bool - rawBuilder *RawBuilder - templateDefinition TemplateDefinition - files map[string]string - buildAssertions func(t *testing.T, dir string, buildErr error) - validateAssertions func(t *testing.T, validateErr error) - } - - testDir := t.TempDir() - validConfigTemplate := `{ - "input": "%s", - "output": "%s" - }` - - testCases := []testCase{ - { - name: "successful raw build yaml output", - validate: true, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/raw.yaml": rawYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.yaml") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), rawBuiltFbcYaml) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "successful raw build json output", - validate: true, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.json")), - }, - files: map[string]string{ - "components/raw.yaml": rawYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), rawBuiltFbcJson) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "invalid template configuration", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(`{ - "invalid": "components/raw.yaml", - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "unmarshalling raw template config:") - }, - }, - { - name: "invalid output type", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "invalid", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/raw.yaml": semverYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("invalid --output value %q, expected (json|yaml)", "invalid")) - }, - }, - { - name: "invalid schema", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: "olm.invalid", - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("schema %q does not match the raw template builder schema %q", "olm.invalid", RawBuilderSchema)) - }, - }, - { - name: "template config has empty input", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(`{ - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "raw template configuration is invalid: raw template config must have a non-empty input (templateDefinition.config.input)") - }, - }, - { - name: "template config has empty output", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(`{ - "input": "components/raw.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "raw template configuration is invalid: raw template config must have a non-empty output (templateDefinition.config.output)") - }, - }, - { - name: "template config has empty input & output", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(`{}`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - buildErr.Error(), - "raw template configuration is invalid: raw template config must have a non-empty input (templateDefinition.config.input),raw template config must have a non-empty output (templateDefinition.config.output)") - }, - }, - { - name: "successful raw build json output using contribution cwd to find inputs", - validate: true, - rawBuilder: NewRawBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - ContributionPath: filepath.Join(testDir, "components"), - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(fmt.Sprintf(validConfigTemplate, "raw.yaml", "catalog.json")), - }, - files: map[string]string{ - "components/raw.yaml": rawYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), rawBuiltFbcJson) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - } - - for i, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - outDir := fmt.Sprintf("raw-%d", i) - outPath := path.Join(testDir, outDir) - err := os.MkdirAll(outPath, 0o777) - require.NoError(t, err) - - // create files in temp dir - for fileName, fileContents := range tc.files { - err := os.MkdirAll(path.Join(testDir, path.Dir(fileName)), 0o777) - require.NoError(t, err) - file, err := os.Create(path.Join(testDir, fileName)) - require.NoError(t, err) - _, err = file.WriteString(fileContents) - require.NoError(t, err) - } - - cacheDir, err := os.MkdirTemp("", "opm-registry-") - require.NoError(t, err) - - reg, err := containerdregistry.NewRegistry( - containerdregistry.WithCacheDir(cacheDir), - ) - defer reg.Destroy() - require.NoError(t, err) - - buildErr := tc.rawBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) - tc.buildAssertions(t, outPath, buildErr) - - if tc.validate { - validateErr := tc.rawBuilder.Validate(context.Background(), outDir) - tc.validateAssertions(t, validateErr) - } - }) - } -} - -const rawYaml = `--- -defaultChannel: preview -name: webhook-operator-412 -schema: olm.package ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator-412 -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator-412 - version: 0.0.1 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle ---- -schema: olm.channel -package: webhook-operator-412 -name: preview -entries: - - name: webhook-operator.v0.0.1 -` - -const rawBuiltFbcYaml = `--- -defaultChannel: preview -name: webhook-operator-412 -schema: olm.package ---- -entries: -- name: webhook-operator.v0.0.1 -name: preview -package: webhook-operator-412 -schema: olm.channel ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator-412 -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator-412 - version: 0.0.1 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle -` - -const rawBuiltFbcJson = `{ - "schema": "olm.package", - "name": "webhook-operator-412", - "defaultChannel": "preview" -} -{ - "schema": "olm.channel", - "name": "preview", - "package": "webhook-operator-412", - "entries": [ - { - "name": "webhook-operator.v0.0.1" - } - ] -} -{ - "schema": "olm.bundle", - "name": "webhook-operator.v0.0.1", - "package": "webhook-operator-412", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3", - "properties": [ - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v1" - } - }, - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v2" - } - }, - { - "type": "olm.package", - "value": { - "packageName": "webhook-operator-412", - "version": "0.0.1" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0=" - } - } - ], - "relatedImages": [ - { - "name": "", - "image": "gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator:0.0.3" - } - ] -} -` - -func TestCustomBuilder(t *testing.T) { - type testCase struct { - name string - validate bool - customBuilder *CustomBuilder - templateDefinition TemplateDefinition - files map[string]string - buildAssertions func(t *testing.T, dir string, buildErr error) - validateAssertions func(t *testing.T, validateErr error) - } - - testDir := t.TempDir() - validTemplateConfig := `{ - "command": "%s", - "args": ["%s"], - "output": "%s" - }` - - testCases := []testCase{ - { - name: "successful custom build yaml output", - validate: true, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(fmt.Sprintf(validTemplateConfig, "cat", path.Join(testDir, "components/custom.yaml"), "catalog.yaml")), - }, - files: map[string]string{ - "components/custom.yaml": customYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.yaml") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), customBuiltFbcYaml) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "successful custom build json output", - validate: true, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "json", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(fmt.Sprintf(validTemplateConfig, "cat", path.Join(testDir, "components/custom.yaml"), "catalog.json")), - }, - files: map[string]string{ - "components/custom.yaml": customYaml, - }, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.NoError(t, buildErr) - // check if the catalog.yaml file exists in the correct place - filePath := path.Join(dir, "catalog.json") - _, err := os.Stat(filePath) - require.NoError(t, err) - file, err := os.Open(filePath) - require.NoError(t, err) - defer file.Close() - fileData, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, string(fileData), customBuiltFbcJson) - }, - validateAssertions: func(t *testing.T, validateErr error) { - require.NoError(t, validateErr) - }, - }, - { - name: "invalid template configuration", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(`{ - "invalid": "components/custom.yaml", - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "unmarshalling custom template config:") - }, - }, - { - name: "invalid schema", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: "olm.invalid", - Config: []byte(`{ - "input": "components/custom.yaml", - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), fmt.Sprintf("schema %q does not match the custom template builder schema %q", "olm.invalid", CustomBuilderSchema)) - }, - }, - { - name: "template config has empty command", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(`{ - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - "custom template configuration is invalid: custom template config must have a non-empty command (templateDefinition.config.command)", - buildErr.Error(), - ) - }, - }, - { - name: "template config has empty output", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(`{ - "command": "ls" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - "custom template configuration is invalid: custom template config must have a non-empty output (templateDefinition.config.output)", - buildErr.Error(), - ) - }, - }, - { - name: "template config has empty command & output", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - WorkingDir: testDir, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(`{}`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Equal(t, - "custom template configuration is invalid: custom template config must have a non-empty command (templateDefinition.config.command),custom template config must have a non-empty output (templateDefinition.config.output)", - buildErr.Error(), - ) - }, - }, - } - - for i, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - outDir := fmt.Sprintf("custom-%d", i) - outPath := path.Join(testDir, outDir) - err := os.MkdirAll(outPath, 0o777) - require.NoError(t, err) - - // create files in temp dir - for fileName, fileContents := range tc.files { - err := os.MkdirAll(path.Join(testDir, path.Dir(fileName)), 0o777) - require.NoError(t, err) - file, err := os.Create(path.Join(testDir, fileName)) - require.NoError(t, err) - _, err = file.WriteString(fileContents) - require.NoError(t, err) - } - - cacheDir, err := os.MkdirTemp("", "opm-registry-") - require.NoError(t, err) - - reg, err := containerdregistry.NewRegistry( - containerdregistry.WithCacheDir(cacheDir), - ) - defer reg.Destroy() - require.NoError(t, err) - - buildErr := tc.customBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) - tc.buildAssertions(t, outPath, buildErr) - - if tc.validate { - validateErr := tc.customBuilder.Validate(context.Background(), outDir) - tc.validateAssertions(t, validateErr) - } - }) - } -} - -const customYaml = `--- -defaultChannel: preview -name: webhook-operator-413 -schema: olm.package ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator-413 -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator-413 - version: 0.0.1 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle ---- -schema: olm.channel -package: webhook-operator-413 -name: preview -entries: - - name: webhook-operator.v0.0.1 -` - -const customBuiltFbcYaml = `--- -defaultChannel: preview -name: webhook-operator-413 -schema: olm.package ---- -entries: -- name: webhook-operator.v0.0.1 -name: preview -package: webhook-operator-413 -schema: olm.channel ---- -image: quay.io/olmtest/webhook-operator-bundle:0.0.3 -name: webhook-operator.v0.0.1 -package: webhook-operator-413 -properties: -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v1 -- type: olm.gvk - value: - group: webhook.operators.coreos.io - kind: WebhookTest - version: v2 -- type: olm.package - value: - packageName: webhook-operator-413 - version: 0.0.1 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= -relatedImages: -- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - name: "" -- image: quay.io/olmtest/webhook-operator-bundle:0.0.3 - name: "" -- image: quay.io/olmtest/webhook-operator:0.0.3 - name: "" -schema: olm.bundle -` - -const customBuiltFbcJson = `{ - "schema": "olm.package", - "name": "webhook-operator-413", - "defaultChannel": "preview" -} -{ - "schema": "olm.channel", - "name": "preview", - "package": "webhook-operator-413", - "entries": [ - { - "name": "webhook-operator.v0.0.1" - } - ] -} -{ - "schema": "olm.bundle", - "name": "webhook-operator.v0.0.1", - "package": "webhook-operator-413", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3", - "properties": [ - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v1" - } - }, - { - "type": "olm.gvk", - "value": { - "group": "webhook.operators.coreos.io", - "kind": "WebhookTest", - "version": "v2" - } - }, - { - "type": "olm.package", - "value": { - "packageName": "webhook-operator-413", - "version": "0.0.1" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0=" - } - } - ], - "relatedImages": [ - { - "name": "", - "image": "gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator-bundle:0.0.3" - }, - { - "name": "", - "image": "quay.io/olmtest/webhook-operator:0.0.3" - } - ] -} -` - -func TestValidateFailure(t *testing.T) { - err := validate(context.Background(), BuilderConfig{}, "") - require.Error(t, err) - require.Contains(t, err.Error(), "no such file or directory") -} diff --git a/alpha/template/composite/composite.go b/alpha/template/composite/composite.go deleted file mode 100644 index e88daa7b2..000000000 --- a/alpha/template/composite/composite.go +++ /dev/null @@ -1,261 +0,0 @@ -package composite - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/url" - "os" - "path/filepath" - - "github.com/operator-framework/operator-registry/pkg/image" - "k8s.io/apimachinery/pkg/util/yaml" -) - -func WithCatalogFile(catalogFile io.Reader) TemplateOption { - return func(t *Template) { - t.catalogFile = catalogFile - } -} - -func WithContributionFile(contribFile io.Reader, contribPath string) TemplateOption { - return func(t *Template) { - t.contributionFile = contribFile - t.contributionPath = contribPath - } -} - -func WithOutputType(outputType string) TemplateOption { - return func(t *Template) { - t.outputType = outputType - } -} - -func WithRegistry(reg image.Registry) TemplateOption { - return func(t *Template) { - t.registry = reg - } -} - -func WithValidate(validate bool) TemplateOption { - return func(t *Template) { - t.validate = validate - } -} - -func NewTemplate(opts ...TemplateOption) *Template { - temp := &Template{ - // Default registered builders when creating a new Template - registeredBuilders: map[string]builderFunc{ - BasicBuilderSchema: func(bc BuilderConfig) Builder { return NewBasicBuilder(bc) }, - SemverBuilderSchema: func(bc BuilderConfig) Builder { return NewSemverBuilder(bc) }, - RawBuilderSchema: func(bc BuilderConfig) Builder { return NewRawBuilder(bc) }, - CustomBuilderSchema: func(bc BuilderConfig) Builder { return NewCustomBuilder(bc) }, - }, - } - - for _, opt := range opts { - opt(temp) - } - - return temp -} - -// FetchCatalogConfig will fetch the catalog configuration file from the given path. -// The path can be a local file path OR a URL that returns the raw contents of the catalog -// configuration file. -// The filepath can be structured relative or as an absolute path -func FetchCatalogConfig(path string, httpGetter HttpGetter) (io.ReadCloser, error) { - var tempCatalog io.ReadCloser - catalogURI, err := url.ParseRequestURI(path) - // Evalute local catalog config - // URI parse will fail on relative filepaths - // Check if path is an absolute filepath - if err != nil || filepath.IsAbs(path) { - tempCatalog, err = os.Open(path) - if err != nil { - return nil, fmt.Errorf("opening catalog config file %q: %v", path, err) - } - } else { - // Evalute remote catalog config - // If URI is valid, execute fetch - tempResp, err := httpGetter.Get(catalogURI.String()) - if err != nil { - return nil, fmt.Errorf("fetching remote catalog config file %q: %v", path, err) - } - tempCatalog = tempResp.Body - } - - return tempCatalog, nil -} - -func (t *Template) Parse() (*Specs, error) { - var s Specs - - catalogSpec, err := t.parseCatalogsSpec() - if err != nil { - return nil, err - } - s.CatalogSpec = catalogSpec - - contributionSpec, err := t.parseContributionSpec() - if err != nil { - return nil, err - } - s.ContributionSpec = contributionSpec - - return &s, nil -} - -func (t *Template) Render(ctx context.Context, validate bool) error { - specs, err := t.Parse() - if err != nil { - return err - } - - catalogBuilderMap, err := t.newCatalogBuilderMap(specs.CatalogSpec.Catalogs, t.outputType) - if err != nil { - return err - } - - // TODO(everettraven): should we return aggregated errors? - for _, component := range specs.ContributionSpec.Components { - if builderMap, ok := (*catalogBuilderMap)[component.Name]; ok { - if builder, ok := builderMap[component.Strategy.Template.Schema]; ok { - // run the builder corresponding to the schema - err := builder.Build(ctx, t.registry, component.Destination.Path, component.Strategy.Template) - if err != nil { - return fmt.Errorf("building component %q: %w", component.Name, err) - } - - if validate { - // run the validation for the builder - err = builder.Validate(ctx, component.Destination.Path) - if err != nil { - return fmt.Errorf("validating component %q: %w", component.Name, err) - } - } - } else { - return fmt.Errorf("building component %q: no builder found for template schema %q", component.Name, component.Strategy.Template.Schema) - } - } else { - allowedComponents := []string{} - for k := range *catalogBuilderMap { - allowedComponents = append(allowedComponents, k) - } - return fmt.Errorf("building component %q: component does not exist in the catalog configuration. Available components are: %s", component.Name, allowedComponents) - } - } - return nil -} - -func (t *Template) builderForSchema(schema string, builderCfg BuilderConfig) (Builder, error) { - builderFunc, ok := t.registeredBuilders[schema] - if !ok { - return nil, fmt.Errorf("unknown schema %q", schema) - } - - return builderFunc(builderCfg), nil -} - -func (t *Template) parseCatalogsSpec() (*CatalogConfig, error) { - - // get catalog configurations - catalogConfig := &CatalogConfig{} - catalogDoc := json.RawMessage{} - catalogDecoder := yaml.NewYAMLOrJSONDecoder(t.catalogFile, 4096) - err := catalogDecoder.Decode(&catalogDoc) - if err != nil { - return nil, fmt.Errorf("decoding catalog config: %v", err) - } - err = json.Unmarshal(catalogDoc, catalogConfig) - if err != nil { - return nil, fmt.Errorf("unmarshalling catalog config: %v", err) - } - - if catalogConfig.Schema != CatalogSchema { - return nil, fmt.Errorf("catalog configuration file has unknown schema, should be %q", CatalogSchema) - } - - return catalogConfig, nil -} - -func (t *Template) parseContributionSpec() (*CompositeConfig, error) { - - // parse data to composite config - compositeConfig := &CompositeConfig{} - compositeDoc := json.RawMessage{} - compositeDecoder := yaml.NewYAMLOrJSONDecoder(t.contributionFile, 4096) - err := compositeDecoder.Decode(&compositeDoc) - if err != nil { - return nil, fmt.Errorf("decoding composite config: %v", err) - } - err = json.Unmarshal(compositeDoc, compositeConfig) - if err != nil { - return nil, fmt.Errorf("unmarshalling composite config: %v", err) - } - - if compositeConfig.Schema != CompositeSchema { - return nil, fmt.Errorf("composite configuration file has unknown schema, should be %q", CompositeSchema) - } - - return compositeConfig, nil -} - -func (t *Template) newCatalogBuilderMap(catalogs []Catalog, outputType string) (*CatalogBuilderMap, error) { - - catalogBuilderMap := make(CatalogBuilderMap) - - // setup the builders for each catalog - setupFailed := false - setupErrors := map[string][]string{} - for _, catalog := range catalogs { - errs := []string{} - // if catalog.Destination.BaseImage == "" { - // errs = append(errs, "destination.baseImage must not be an empty string") - // } - - if catalog.Destination.WorkingDir == "" { - errs = append(errs, "destination.workingDir must not be an empty string") - } - - // check for validation errors and skip builder creation if there are any errors - if len(errs) > 0 { - setupFailed = true - setupErrors[catalog.Name] = errs - continue - } - - if _, ok := catalogBuilderMap[catalog.Name]; !ok { - builderMap := make(BuilderMap) - for _, schema := range catalog.Builders { - builder, err := t.builderForSchema(schema, BuilderConfig{ - WorkingDir: catalog.Destination.WorkingDir, - OutputType: outputType, - ContributionPath: t.contributionPath, - }) - if err != nil { - return nil, fmt.Errorf("getting builder %q for catalog %q: %v", schema, catalog.Name, err) - } - builderMap[schema] = builder - } - catalogBuilderMap[catalog.Name] = builderMap - } - } - - // if there were errors validating the catalog configuration then exit - if setupFailed { - //build the error message - var errMsg string - for cat, errs := range setupErrors { - errMsg += fmt.Sprintf("\nCatalog %v:\n", cat) - for _, err := range errs { - errMsg += fmt.Sprintf(" - %v\n", err) - } - } - return nil, fmt.Errorf("catalog configuration file field validation failed: %s", errMsg) - } - - return &catalogBuilderMap, nil -} diff --git a/alpha/template/composite/composite_test.go b/alpha/template/composite/composite_test.go deleted file mode 100644 index f46f39446..000000000 --- a/alpha/template/composite/composite_test.go +++ /dev/null @@ -1,615 +0,0 @@ -package composite - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "path" - "strings" - "testing" - - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/stretchr/testify/require" -) - -var _ Builder = &TestBuilder{} - -var TestBuilderSchema = "olm.builder.test" - -type TestBuilder struct { - buildShouldError bool - validateShouldError bool -} - -func (tb *TestBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { - if tb.buildShouldError { - return fmt.Errorf("build error!") - } - return nil -} - -func (tb *TestBuilder) Validate(ctx context.Context, dir string) error { - if tb.validateShouldError { - return fmt.Errorf("validate error!") - } - return nil -} - -var renderValidCatalog = ` -schema: olm.composite.catalogs -catalogs: - - name: first-catalog - destination: - workingDir: contributions/first-catalog - builders: - - olm.builder.test -` - -var renderValidComposite = ` -schema: olm.composite -components: - - name: first-catalog - destination: - path: my-operator - strategy: - name: test - template: - schema: olm.builder.test - config: - input: components/contribution1.yaml - output: catalog.yaml -` - -var renderInvalidComponentComposite = ` -schema: olm.composite -components: - - name: missing-catalog - destination: - path: my-operator - strategy: - name: test - template: - schema: olm.builder.test - config: - input: components/contribution1.yaml - output: catalog.yaml -` - -var renderInvalidBuilderComposite = ` -schema: olm.composite -components: - - name: first-catalog - destination: - path: my-operator - strategy: - name: test - template: - schema: olm.builder.invalid - config: - input: components/contribution1.yaml - output: catalog.yaml -` - -func TestCompositeRender(t *testing.T) { - type testCase struct { - name string - compositeTemplate Template - validate bool - assertions func(t *testing.T, err error) - } - - testCases := []testCase{ - { - name: "successful render", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderValidComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{} }, - }, - }, - assertions: func(t *testing.T, err error) { - require.NoError(t, err) - }, - }, - { - name: "Component build failure", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderValidComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{buildShouldError: true} }, - }, - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - require.Equal(t, "building component \"first-catalog\": build error!", err.Error()) - }, - }, - { - name: "Component validate failure", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderValidComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{validateShouldError: true} }, - }, - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - require.Equal(t, "validating component \"first-catalog\": validate error!", err.Error()) - }, - }, - { - name: "Skipping validation", - validate: false, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderValidComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{validateShouldError: true} }, - }, - }, - assertions: func(t *testing.T, err error) { - // We are skipping validation so we shouldn't receive - // the validation error from the TestBuilder - require.NoError(t, err) - }, - }, - { - name: "component not in catalog config", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderInvalidComponentComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{} }, - }, - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - expectedErr := fmt.Sprintf("building component %q: component does not exist in the catalog configuration. Available components are: %s", "missing-catalog", []string{"first-catalog"}) - require.Equal(t, expectedErr, err.Error()) - }, - }, - { - name: "builder not in catalog config", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(renderInvalidBuilderComposite), - registeredBuilders: map[string]builderFunc{ - TestBuilderSchema: func(bc BuilderConfig) Builder { return &TestBuilder{} }, - }, - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - require.Equal(t, "building component \"first-catalog\": no builder found for template schema \"olm.builder.invalid\"", err.Error()) - }, - }, - { - name: "error parsing catalog spec", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(invalidSchemaCatalog), - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - require.Equal(t, "catalog configuration file has unknown schema, should be \"olm.composite.catalogs\"", err.Error()) - }, - }, - { - name: "error parsing contribution spec", - validate: true, - compositeTemplate: Template{ - catalogFile: strings.NewReader(renderValidCatalog), - contributionFile: strings.NewReader(invalidSchemaComposite), - }, - assertions: func(t *testing.T, err error) { - require.Error(t, err) - require.Equal(t, "composite configuration file has unknown schema, should be \"olm.composite\"", err.Error()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.compositeTemplate.Render(context.Background(), tc.validate) - tc.assertions(t, err) - }) - } -} - -func TestBuilderForSchema(t *testing.T) { - type testCase struct { - name string - builderSchema string - builderCfg BuilderConfig - assertions func(t *testing.T, builder Builder, err error) - } - - testCases := []testCase{ - { - name: "Basic Builder Schema", - builderSchema: BasicBuilderSchema, - builderCfg: BuilderConfig{}, - assertions: func(t *testing.T, builder Builder, err error) { - require.NoError(t, err) - require.IsType(t, &BasicBuilder{}, builder) - }, - }, - { - name: "Semver Builder Schema", - builderSchema: SemverBuilderSchema, - builderCfg: BuilderConfig{}, - assertions: func(t *testing.T, builder Builder, err error) { - require.NoError(t, err) - require.IsType(t, &SemverBuilder{}, builder) - }, - }, - { - name: "Raw Builder Schema", - builderSchema: RawBuilderSchema, - builderCfg: BuilderConfig{}, - assertions: func(t *testing.T, builder Builder, err error) { - require.NoError(t, err) - require.IsType(t, &RawBuilder{}, builder) - }, - }, - { - name: "Custom Builder Schema", - builderSchema: CustomBuilderSchema, - builderCfg: BuilderConfig{}, - assertions: func(t *testing.T, builder Builder, err error) { - require.NoError(t, err) - require.IsType(t, &CustomBuilder{}, builder) - }, - }, - { - name: "Invalid Builder Schema", - builderSchema: "invalid", - builderCfg: BuilderConfig{}, - assertions: func(t *testing.T, builder Builder, err error) { - require.Error(t, err) - require.Equal(t, fmt.Sprintf("unknown schema %q", "invalid"), err.Error()) - require.Nil(t, builder) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - template := NewTemplate() - builder, err := template.builderForSchema(tc.builderSchema, tc.builderCfg) - tc.assertions(t, builder, err) - }) - } - -} - -var validCatalog = ` -schema: olm.composite.catalogs -catalogs: - - name: first-catalog - destination: - workingDir: contributions/first-catalog - builders: - - olm.builder.semver - - olm.builder.basic - - name: second-catalog - destination: - workingDir: contributions/second-catalog - builders: - - olm.builder.semver - - name: test-catalog - destination: - workingDir: contributions/test-catalog - builders: - - olm.builder.custom` - -var unmarshalFail = ` -invalid -` - -var invalidSchemaCatalog = ` -schema: invalid -catalogs: - - name: first-catalog - destination: - workingDir: contributions/first-catalog - builders: - - olm.builder.semver - - olm.builder.basic -` - -func TestParseCatalogSpec(t *testing.T) { - type testCase struct { - name string - catalog string - assertions func(t *testing.T, catalog *CatalogConfig, err error) - } - - testCases := []testCase{ - { - name: "Valid catalog configuration", - catalog: validCatalog, - assertions: func(t *testing.T, catalog *CatalogConfig, err error) { - require.NoError(t, err) - require.Equal(t, 3, len(catalog.Catalogs)) - }, - }, - { - name: "Unmarshal failure", - catalog: unmarshalFail, - assertions: func(t *testing.T, catalog *CatalogConfig, err error) { - require.Error(t, err) - require.Equal(t, "unmarshalling catalog config: json: cannot unmarshal string into Go value of type composite.CatalogConfig", err.Error()) - }, - }, - { - name: "Invalid schema", - catalog: invalidSchemaCatalog, - assertions: func(t *testing.T, catalog *CatalogConfig, err error) { - require.Error(t, err) - require.Equal(t, fmt.Sprintf("catalog configuration file has unknown schema, should be %q", CatalogSchema), err.Error()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - template := NewTemplate(WithCatalogFile(strings.NewReader(tc.catalog))) - catalog, err := template.parseCatalogsSpec() - tc.assertions(t, catalog, err) - }) - } -} - -var validComposite = ` -schema: olm.composite -components: - - name: first-catalog - destination: - path: my-operator - strategy: - name: semver - template: - schema: olm.builder.semver - config: - input: components/contribution1.yaml - output: catalog.yaml -` - -var invalidSchemaComposite = ` -schema: invalid -components: - - name: first-catalog - destination: - path: my-operator - strategy: - name: semver - template: - schema: olm.builder.semver - config: - input: components/contribution1.yaml - output: catalog.yaml -` - -func TestParseContributionSpec(t *testing.T) { - type testCase struct { - name string - composite string - assertions func(t *testing.T, composite *CompositeConfig, err error) - } - - testCases := []testCase{ - { - name: "Valid composite", - composite: validComposite, - assertions: func(t *testing.T, composite *CompositeConfig, err error) { - require.NoError(t, err) - require.Equal(t, 1, len(composite.Components)) - }, - }, - { - name: "Unmarshal failure", - composite: unmarshalFail, - assertions: func(t *testing.T, composite *CompositeConfig, err error) { - require.Error(t, err) - require.Equal(t, "unmarshalling composite config: json: cannot unmarshal string into Go value of type composite.CompositeConfig", err.Error()) - }, - }, - { - name: "Invalid schema", - composite: invalidSchemaComposite, - assertions: func(t *testing.T, composite *CompositeConfig, err error) { - require.Error(t, err) - require.Equal(t, fmt.Sprintf("composite configuration file has unknown schema, should be %q", CompositeSchema), err.Error()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - template := NewTemplate(WithContributionFile(strings.NewReader(tc.composite), "")) - contrib, err := template.parseContributionSpec() - tc.assertions(t, contrib, err) - }) - } -} - -func TestNewCatalogBuilderMap(t *testing.T) { - type testCase struct { - name string - catalogs []Catalog - assertions func(t *testing.T, builderMap *CatalogBuilderMap, err error) - } - - testCases := []testCase{ - { - name: "Valid Catalogs", - catalogs: []Catalog{ - { - Name: "test-catalog", - Destination: CatalogDestination{ - WorkingDir: "/", - // BaseImage: "base", - }, - Builders: []string{ - BasicBuilderSchema, - }, - }, - }, - assertions: func(t *testing.T, builderMap *CatalogBuilderMap, err error) { - require.NoError(t, err) - //TODO: More assertions here - }, - }, - { - name: "Invalid Builder", - catalogs: []Catalog{ - { - Name: "test-catalog", - Destination: CatalogDestination{ - WorkingDir: "/", - // BaseImage: "base", - }, - Builders: []string{ - "invalid", - }, - }, - }, - assertions: func(t *testing.T, builderMap *CatalogBuilderMap, err error) { - require.Error(t, err) - require.Equal(t, "getting builder \"invalid\" for catalog \"test-catalog\": unknown schema \"invalid\"", err.Error()) - }, - }, - // { - // name: "BaseImage+WorkingDir invalid", - // catalogs: []Catalog{ - // { - // Name: "test-catalog", - // Destination: CatalogDestination{}, - // Builders: []string{ - // BasicBuilderSchema, - // }, - // }, - // }, - // assertions: func(t *testing.T, builderMap *CatalogBuilderMap, err error) { - // require.Error(t, err) - // require.Equal(t, "catalog configuration file field validation failed: \nCatalog test-catalog:\n - destination.baseImage must not be an empty string\n - destination.workingDir must not be an empty string\n", err.Error()) - // }, - // }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - template := NewTemplate() - builderMap, err := template.newCatalogBuilderMap(tc.catalogs, "yaml") - tc.assertions(t, builderMap, err) - }) - } -} - -type fakeGetter struct { - catalog string - shouldError bool -} - -func (fg *fakeGetter) Get(url string) (*http.Response, error) { - if fg.shouldError { - return nil, fmt.Errorf("error!") - } - - return &http.Response{ - Body: io.NopCloser(strings.NewReader(fg.catalog)), - }, nil -} - -func TestFetchCatalogConfig(t *testing.T) { - type testCase struct { - name string - fakeGetter *fakeGetter - path string - createFile bool - assertions func(t *testing.T, rc io.ReadCloser, err error) - } - - testDir := t.TempDir() - - testCases := []testCase{ - { - name: "Successful HTTP fetch", - path: "http://some-path.com", - fakeGetter: &fakeGetter{ - catalog: validCatalog, - }, - assertions: func(t *testing.T, rc io.ReadCloser, err error) { - require.NoError(t, err) - require.NotNil(t, rc) - }, - }, - { - name: "Failed HTTP fetch", - path: "http://some-path.com", - fakeGetter: &fakeGetter{ - catalog: validCatalog, - shouldError: true, - }, - assertions: func(t *testing.T, rc io.ReadCloser, err error) { - require.Error(t, err) - require.Equal(t, "fetching remote catalog config file \"http://some-path.com\": error!", err.Error()) - }, - }, - // TODO: for some reason this is triggering the fakeGetter.Get() function instead of using os.Open() - { - name: "Successful file fetch", - path: "file/test.yaml", - fakeGetter: &fakeGetter{ - catalog: validCatalog, - shouldError: true, - }, - createFile: true, - assertions: func(t *testing.T, rc io.ReadCloser, err error) { - require.NoError(t, err) - require.NotNil(t, rc) - }, - }, - { - name: "Failed file fetch", - path: "file/test.yaml", - fakeGetter: &fakeGetter{ - catalog: validCatalog, - }, - createFile: false, - assertions: func(t *testing.T, rc io.ReadCloser, err error) { - require.Error(t, err) - require.Equal(t, "opening catalog config file \"file/test.yaml\": open file/test.yaml: no such file or directory", err.Error()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - filepath := tc.path - if tc.createFile { - err := os.MkdirAll(path.Join(testDir, path.Dir(tc.path)), 0o777) - require.NoError(t, err) - file, err := os.Create(path.Join(testDir, tc.path)) - require.NoError(t, err) - _, err = file.WriteString(tc.fakeGetter.catalog) - require.NoError(t, err) - - filepath = path.Join(testDir, tc.path) - } - - rc, err := FetchCatalogConfig(filepath, tc.fakeGetter) - tc.assertions(t, rc, err) - }) - } -} diff --git a/alpha/template/composite/config.go b/alpha/template/composite/config.go deleted file mode 100644 index 21d4ac5ee..000000000 --- a/alpha/template/composite/config.go +++ /dev/null @@ -1,42 +0,0 @@ -package composite - -const ( - CompositeSchema = "olm.composite" - CatalogSchema = "olm.composite.catalogs" -) - -type CompositeConfig struct { - Schema string - Components []Component -} - -type Component struct { - Name string - Destination ComponentDestination - Strategy BuildStrategy -} - -type ComponentDestination struct { - Path string -} - -type BuildStrategy struct { - Name string - Template TemplateDefinition -} - -type CatalogConfig struct { - Schema string - Catalogs []Catalog -} - -type Catalog struct { - Name string - Destination CatalogDestination - Builders []string -} - -type CatalogDestination struct { - // BaseImage string - WorkingDir string -} diff --git a/alpha/template/composite/types.go b/alpha/template/composite/types.go deleted file mode 100644 index 32116bea1..000000000 --- a/alpha/template/composite/types.go +++ /dev/null @@ -1,62 +0,0 @@ -package composite - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/operator-framework/operator-registry/pkg/image" -) - -type TemplateDefinition struct { - Schema string - Config json.RawMessage -} - -type BasicConfig struct { - Input string - Output string -} - -type SemverConfig struct { - Input string - Output string -} - -type RawConfig struct { - Input string - Output string -} - -type CustomConfig struct { - Command string - Args []string - Output string -} - -type BuilderMap map[string]Builder - -type CatalogBuilderMap map[string]BuilderMap - -type builderFunc func(BuilderConfig) Builder - -type Template struct { - catalogFile io.Reader - contributionFile io.Reader - contributionPath string - validate bool - outputType string - registry image.Registry - registeredBuilders map[string]builderFunc -} - -type TemplateOption func(t *Template) - -type Specs struct { - CatalogSpec *CatalogConfig - ContributionSpec *CompositeConfig -} - -type HttpGetter interface { - Get(url string) (*http.Response, error) -} diff --git a/alpha/template/semver/README.md b/alpha/template/semver/README.md deleted file mode 100644 index ace33746a..000000000 --- a/alpha/template/semver/README.md +++ /dev/null @@ -1,286 +0,0 @@ -## Semver Template: - -Since a `catalog template` is identified as an input schema which may be processed to generate a valid FBC, we can define a `semver template` as a schema which uses channel conventions to facilitate the auto-generation of channels along `semver` delimiters. - -[**DISCLAIMER:** since version build metadata [MUST be ignored when determining version precedence](https://semver.org) when using semver, rendering the template will result in an error if two bundles differ only by the build metadata.] - -### Schema Goals -The `semver template` must have: -- terse grammar to minimize creation/maintenance effort -- deterministic output -- simple channel promotion for maturing bundles -- demonstration of a common type of channel maturity model -- minor-version (Y-stream), major-version (X-stream) versioning optionality - -The resulting FBC must clearly indicate how generated channels relate to template entities - -### Schema Anatomy -For convenience and simplicity, this template currently supports hard-coded channel names `Candidate`, `Fast`, and `Stable`, in order of increasing channel stability. We leverage this relationship to calculate the default channel for the package. - -`GenerateMajorChannels` and `GenerateMinorChannels` dictate whether this template will generate X-stream or Y-stream channels (attributes can be set independently). If omitted, only minor (Y-stream) channels will be generated. - -Under each channel are a list of bundle image references which contribute to that channel. - -With the following (hypothetical) example we define a mock bundle which has 11 versions, represented across each of the channel types: -```yaml -Schema: olm.semver -GenerateMajorChannels: true -GenerateMinorChannels: true -Candidate: - Bundles: - - Image: quay.io/foo/olm:testoperator.v0.1.0 - - Image: quay.io/foo/olm:testoperator.v0.1.1 - - Image: quay.io/foo/olm:testoperator.v0.1.2 - - Image: quay.io/foo/olm:testoperator.v0.1.3 - - Image: quay.io/foo/olm:testoperator.v0.2.0 - - Image: quay.io/foo/olm:testoperator.v0.2.1 - - Image: quay.io/foo/olm:testoperator.v0.2.2 - - Image: quay.io/foo/olm:testoperator.v0.3.0 - - Image: quay.io/foo/olm:testoperator.v1.0.0 - - Image: quay.io/foo/olm:testoperator.v1.0.1 - - Image: quay.io/foo/olm:testoperator.v1.1.0 -Fast: - Bundles: - - Image: quay.io/foo/olm:testoperator.v0.2.1 - - Image: quay.io/foo/olm:testoperator.v0.2.2 - - Image: quay.io/foo/olm:testoperator.v0.3.0 - - Image: quay.io/foo/olm:testoperator.v1.0.1 - - Image: quay.io/foo/olm:testoperator.v1.1.0 -Stable: - Bundles: - - Image: quay.io/foo/olm:testoperator.v1.0.1 -``` -In this example, `Candidate` has the entire version range of bundles, `Fast` has a mix of older and more-recent versions, and `Stable` channel only has a single published entry. - -### CLI Tool Usage -``` -% ./bin/opm alpha render-template semver -h -Generate a file-based catalog from a single 'semver template' file -When FILE is '-' or not provided, the template is read from standard input - -Usage: - opm alpha render-template semver [FILE] [flags] - -Flags: - -h, --help help for semver - -o, --output string Output format (json|yaml|mermaid) (default "json") - -Global Flags: - --skip-tls-verify skip TLS certificate verification for container image registries while pulling bundles - --use-http use plain HTTP for container image registries while pulling bundles -``` - -Example command usage: -``` -# Example with file argument passed in -opm alpha render-template semver infile.semver.template.yaml - -# Example with no file argument passed in -opm alpha render-template semver -o yaml < infile.semver.template.yaml > outfile.yaml - -# Example with "-" as the file argument passed in -cat infile.semver.template.yaml | opm alpha render-template semver -o mermaid - -``` -Note that if the command is called without a file argument and nothing passed in on standard input, -the command will hang indefinitely. Either a file argument or file information passed -in on standard input is required by the command. - -With the template attribute `GenerateMajorChannels: true` resulting major channels from the command are (filtering out `olm.bundle` content): -```yaml ---- -defaultChannel: stable-v1 -name: testoperator -schema: olm.package ---- -entries: - - name: testoperator.v0.1.0 - - name: testoperator.v0.1.1 - - name: testoperator.v0.1.2 - - name: testoperator.v0.1.3 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 - - name: testoperator.v0.2.0 - - name: testoperator.v0.2.1 - - name: testoperator.v0.2.2 - replaces: testoperator.v0.1.3 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 - - testoperator.v0.2.0 - - testoperator.v0.2.1 - - name: testoperator.v0.3.0 - replaces: testoperator.v0.2.2 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 - - testoperator.v0.1.3 - - testoperator.v0.2.0 - - testoperator.v0.2.1 -name: candidate-v0 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.0 - - name: testoperator.v1.0.1 - skips: - - testoperator.v1.0.0 - - name: testoperator.v1.1.0 - replaces: testoperator.v1.0.1 - skips: - - testoperator.v1.0.0 -name: candidate-v1 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v0.2.1 - - name: testoperator.v0.2.2 - skips: - - testoperator.v0.2.1 - - name: testoperator.v0.3.0 - replaces: testoperator.v0.2.2 - skips: - - testoperator.v0.2.1 -name: fast-v0 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.1 - - name: testoperator.v1.1.0 - replaces: testoperator.v1.0.1 -name: fast-v1 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.1 -name: stable-v1 -package: testoperator -schema: olm.channel -``` - -We generated a channel for each template channel entity corresponding to each of the 0.\#.\#, 1.\#.\# major version ranges with skips to the head of the highest semver in a channel. We also generated a replaces edge to traverse across minor version transitions within each major channel. Finally, we generated an `olm.package` object, setting as default the most-stable channel head we created. This process will prefer `Stable` channel over `Fast`, over `Candidate` and then a higher bundle version over a lower version. -(Please note that the naming of the generated channels indicates the digits of significance for that channel. For example, `fast-v1` is a decomposed channel of the `fast` type which contains only major versions of contributing bundles matching `v1`.) - -For contrast, with the template attribute `GenerateMinorChannels: true` and running the command again (again skipping rendered bundle image output) we get a bunch more channels: -```yaml ---- -defaultChannel: stable-v1.0 -name: testoperator -schema: olm.package ---- -entries: - - name: testoperator.v0.1.0 - - name: testoperator.v0.1.1 - - name: testoperator.v0.1.2 - - name: testoperator.v0.1.3 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 -name: candidate-v0.1 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v0.2.0 - - name: testoperator.v0.2.1 - - name: testoperator.v0.2.2 - replaces: testoperator.v0.1.3 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 - - testoperator.v0.2.0 - - testoperator.v0.2.1 -name: candidate-v0.2 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v0.3.0 - replaces: testoperator.v0.2.2 - skips: - - testoperator.v0.1.0 - - testoperator.v0.1.1 - - testoperator.v0.1.2 - - testoperator.v0.1.3 - - testoperator.v0.2.0 - - testoperator.v0.2.1 -name: candidate-v0.3 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.0 - - name: testoperator.v1.0.1 - skips: - - testoperator.v1.0.0 -name: candidate-v1.0 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.1.0 - replaces: testoperator.v1.0.1 - skips: - - testoperator.v1.0.0 -name: candidate-v1.1 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v0.2.1 - - name: testoperator.v0.2.2 - skips: - - testoperator.v0.2.1 -name: fast-v0.2 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v0.3.0 - replaces: testoperator.v0.2.2 - skips: - - testoperator.v0.2.1 -name: fast-v0.3 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.1 -name: fast-v1.0 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.1.0 - replaces: testoperator.v1.0.1 -name: fast-v1.1 -package: testoperator -schema: olm.channel ---- -entries: - - name: testoperator.v1.0.1 -name: stable-v1.0 -package: testoperator -schema: olm.channel -``` -Here, a channel is generated for each template channel which differs by minor version, each channel has a `replaces` edge from the highest version entry in the predecessor channel, and the highest version entry in each channel also has a skips list composed of all lower version entries within the same minor (Y). Please note that at no time do we transgress across major-version boundaries with the channels, to be consistent with [the semver convention](https://semver.org/) for major versions, where the purpose is to make incompatible API changes. - - -### DEMOS - -#### Major Channel Generation -![`GenerateMajorChannels`](./major-version-demo.gif) - -#### Minor Channel Generation -![`GenerateMinorChannels`](./minor-version-demo.gif) - - diff --git a/alpha/template/semver/major-version-demo.gif b/alpha/template/semver/major-version-demo.gif deleted file mode 100644 index 21d46519c..000000000 Binary files a/alpha/template/semver/major-version-demo.gif and /dev/null differ diff --git a/alpha/template/semver/minor-version-demo.gif b/alpha/template/semver/minor-version-demo.gif deleted file mode 100644 index fbe9bf1ac..000000000 Binary files a/alpha/template/semver/minor-version-demo.gif and /dev/null differ diff --git a/alpha/template/semver/semver.go b/alpha/template/semver/semver.go deleted file mode 100644 index a580fbc01..000000000 --- a/alpha/template/semver/semver.go +++ /dev/null @@ -1,466 +0,0 @@ -package semver - -import ( - "context" - "fmt" - "io" - "sort" - - "github.com/operator-framework/operator-registry/alpha/action" - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/property" - - "github.com/blang/semver/v4" - "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/yaml" -) - -func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) { - var out declcfg.DeclarativeConfig - - sv, err := readFile(t.Data) - if err != nil { - return nil, fmt.Errorf("render: unable to read file: %v", err) - } - - var cfgs []declcfg.DeclarativeConfig - - bundleDict := make(map[string]struct{}) - buildBundleList(&sv.Candidate.Bundles, &bundleDict) - buildBundleList(&sv.Fast.Bundles, &bundleDict) - buildBundleList(&sv.Stable.Bundles, &bundleDict) - - for b := range bundleDict { - r := action.Render{ - AllowedRefMask: action.RefBundleImage, - Refs: []string{b}, - Registry: t.Registry, - } - c, err := r.Run(ctx) - if err != nil { - return nil, err - } - cfgs = append(cfgs, *c) - } - out = *combineConfigs(cfgs) - - if len(out.Bundles) == 0 { - return nil, fmt.Errorf("render: no bundles specified or no bundles could be rendered") - } - - channelBundleVersions, err := sv.getVersionsFromStandardChannels(&out) - if err != nil { - return nil, fmt.Errorf("render: unable to post-process bundle info: %v", err) - } - - channels := sv.generateChannels(channelBundleVersions) - out.Channels = channels - out.Packages[0].DefaultChannel = sv.defaultChannel - - return &out, nil -} - -func buildBundleList(bundles *[]semverTemplateBundleEntry, dict *map[string]struct{}) { - for _, b := range *bundles { - if _, ok := (*dict)[b.Image]; !ok { - (*dict)[b.Image] = struct{}{} - } - } -} - -func readFile(reader io.Reader) (*semverTemplate, error) { - data, err := io.ReadAll(reader) - if err != nil { - return nil, err - } - - sv := semverTemplate{} - if err := yaml.UnmarshalStrict(data, &sv); err != nil { - return nil, err - } - - if sv.Schema != schema { - return nil, fmt.Errorf("readFile: input file has unknown schema, should be %q", schema) - } - - // if no generate option is selected, default to GenerateMinorChannels - if !sv.GenerateMajorChannels && !sv.GenerateMinorChannels { - sv.GenerateMinorChannels = true - } - - // for default channel preference, - // if un-set, default to align to the selected generate option - // if set, error out if we mismatch the two - switch sv.DefaultChannelTypePreference { - case defaultStreamType: - if sv.GenerateMinorChannels { - sv.DefaultChannelTypePreference = minorStreamType - } else if sv.GenerateMajorChannels { - sv.DefaultChannelTypePreference = majorStreamType - } - case minorStreamType: - if !sv.GenerateMinorChannels { - return nil, fmt.Errorf("schema attribute mismatch: DefaultChannelTypePreference set to 'minor' doesn't make sense if not generating minor-version channels") - } - case majorStreamType: - if !sv.GenerateMajorChannels { - return nil, fmt.Errorf("schema attribute mismatch: DefaultChannelTypePreference set to 'major' doesn't make sense if not generating major-version channels") - } - default: - return nil, fmt.Errorf("unknown DefaultChannelTypePreference: %q\nValid values are 'major' or 'minor'", sv.DefaultChannelTypePreference) - } - - return &sv, nil -} - -func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.DeclarativeConfig) (*bundleVersions, error) { - versions := bundleVersions{} - - bdm, err := sv.getVersionsFromChannel(sv.Candidate.Bundles, cfg) - if err != nil { - return nil, err - } - if err = validateVersions(&bdm); err != nil { - return nil, err - } - versions[candidateChannelArchetype] = bdm - - bdm, err = sv.getVersionsFromChannel(sv.Fast.Bundles, cfg) - if err != nil { - return nil, err - } - if err = validateVersions(&bdm); err != nil { - return nil, err - } - versions[fastChannelArchetype] = bdm - - bdm, err = sv.getVersionsFromChannel(sv.Stable.Bundles, cfg) - if err != nil { - return nil, err - } - if err = validateVersions(&bdm); err != nil { - return nil, err - } - versions[stableChannelArchetype] = bdm - - return &versions, nil -} - -func (sv *semverTemplate) getVersionsFromChannel(semverBundles []semverTemplateBundleEntry, cfg *declcfg.DeclarativeConfig) (map[string]semver.Version, error) { - entries := make(map[string]semver.Version) - - // we iterate over the channel bundles from the template, to: - // - identify if any required bundles for the channel are missing/not rendered/otherwise unavailable - // - maintain the channel-bundle relationship as we map from un-rendered semver template bundles to rendered bundles in `entries` which is accumulated by the caller - // in a per-channel structure to which we can safely refer when generating/linking channels - for _, semverBundle := range semverBundles { - // test if the bundle specified in the template is present in the successfully-rendered bundles - index := 0 - for index < len(cfg.Bundles) { - if cfg.Bundles[index].Image == semverBundle.Image { - break - } - index++ - } - if index == len(cfg.Bundles) { - return nil, fmt.Errorf("supplied bundle image name %q not found in rendered bundle images", semverBundle.Image) - } - b := cfg.Bundles[index] - - props, err := property.Parse(b.Properties) - if err != nil { - return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) - } - if len(props.Packages) != 1 { - return nil, fmt.Errorf("bundle %q has multiple %q properties, expected exactly 1", b.Name, property.TypePackage) - } - v, err := semver.Parse(props.Packages[0].Version) - if err != nil { - return nil, fmt.Errorf("bundle %q has invalid version %q: %v", b.Name, props.Packages[0].Version, err) - } - - // package name detection - if sv.pkg != "" { - // if we have a known package name, then ensure all subsequent packages match - if props.Packages[0].PackageName != sv.pkg { - return nil, fmt.Errorf("bundle %q does not belong to this package: %q", props.Packages[0].PackageName, sv.pkg) - } - } else { - // else cache the first - p := newPackage(props.Packages[0].PackageName) - cfg.Packages = append(cfg.Packages, *p) - sv.pkg = props.Packages[0].PackageName - } - - if _, ok := entries[b.Name]; ok { - return nil, fmt.Errorf("duplicate bundle name %q", b.Name) - } - - entries[b.Name] = v - } - - return entries, nil -} - -// generates an unlinked channel for each channel as per the input template config (major || minor), then link up the edges of the set of channels so that: -// - for minor version increase, the new edge replaces the previous -// - (for major channels) iterating to a new minor version channel (traversing between Y-streams) creates a 'replaces' edge between the predecessor and successor bundles -// - within the same minor version (Y-stream), the head of the channel should have a 'skips' encompassing all lesser Y.Z versions of the bundle enumerated in the template. -// along the way, uses a highwaterChannel marker to identify the "most stable" channel head to be used as the default channel for the generated package - -func (sv *semverTemplate) generateChannels(semverChannels *bundleVersions) []declcfg.Channel { - outChannels := []declcfg.Channel{} - - // sort the channel archetypes in ascending order so we can traverse the bundles in order of - // their source channel's priority - var archetypesByPriority []channelArchetype - for k := range channelPriorities { - archetypesByPriority = append(archetypesByPriority, k) - } - sort.Sort(byChannelPriority(archetypesByPriority)) - - // set to the least-priority channel - hwc := highwaterChannel{archetype: archetypesByPriority[0], version: semver.Version{Major: 0, Minor: 0}} - - unlinkedChannels := make(map[string]*declcfg.Channel) - - for _, archetype := range archetypesByPriority { - bundles := (*semverChannels)[archetype] - // skip channel if empty - if len(bundles) == 0 { - continue - } - - // sort the bundle names according to their semver, so we can walk in ascending order - bundleNamesByVersion := []string{} - for b := range bundles { - bundleNamesByVersion = append(bundleNamesByVersion, b) - } - sort.Slice(bundleNamesByVersion, func(i, j int) bool { - return bundles[bundleNamesByVersion[i]].LT(bundles[bundleNamesByVersion[j]]) - }) - - // for each bundle (by version): - // for each of Major/Minor setting (since they're independent) - // retrieve the existing channel object, or create a channel (by criteria major/minor) if one doesn't exist - // add a new edge entry based on the bundle name - // save the channel name --> channel archetype mapping - // test the channel object for 'more stable' than previous best - for _, bundleName := range bundleNamesByVersion { - // a dodge to avoid duplicating channel processing body; accumulate a map of the channels which need creating from the bundle - // we need to associate by kind so we can partition the resulting entries - channelNameKeys := make(map[streamType]string) - if sv.GenerateMajorChannels { - channelNameKeys[majorStreamType] = channelNameFromMajor(archetype, bundles[bundleName]) - } - if sv.GenerateMinorChannels { - channelNameKeys[minorStreamType] = channelNameFromMinor(archetype, bundles[bundleName]) - } - - for cKey, cName := range channelNameKeys { - ch, ok := unlinkedChannels[cName] - if !ok { - ch = newChannel(sv.pkg, cName) - - unlinkedChannels[cName] = ch - - hwcCandidate := highwaterChannel{archetype: archetype, kind: cKey, version: bundles[bundleName], name: cName} - if hwcCandidate.gt(&hwc, sv.DefaultChannelTypePreference) { - hwc = hwcCandidate - } - } - ch.Entries = append(ch.Entries, declcfg.ChannelEntry{Name: bundleName}) - } - } - } - - // save off the name of the high-water-mark channel for the default for this package - sv.defaultChannel = hwc.name - - outChannels = append(outChannels, sv.linkChannels(unlinkedChannels, semverChannels)...) - - return outChannels -} - -func (sv *semverTemplate) linkChannels(unlinkedChannels map[string]*declcfg.Channel, harvestedVersions *bundleVersions) []declcfg.Channel { - channels := []declcfg.Channel{} - - // bundle --> version lookup - bundleVersions := make(map[string]semver.Version) - for _, vs := range *harvestedVersions { - for b, v := range vs { - if _, ok := bundleVersions[b]; !ok { - bundleVersions[b] = v - } - } - } - - for _, channel := range unlinkedChannels { - entries := &channel.Entries - sort.Slice(*entries, func(i, j int) bool { - return bundleVersions[(*entries)[i].Name].LT(bundleVersions[(*entries)[j].Name]) - }) - - // "inchworm" through the sorted entries, iterating curEdge but extending yProbe to the next Y-transition - // then catch up curEdge to yProbe as 'skips', and repeat until we reach the end of the entries - // finally, because the inchworm will always fail to pick up the last Y-transition, we test for it and link it up as a 'replaces' - curEdge, yProbe := 0, 0 - zmaxQueue := "" - entryCount := len(*entries) - - for curEdge < entryCount { - for yProbe < entryCount { - curVersion := bundleVersions[(*entries)[curEdge].Name] - yProbeVersion := bundleVersions[(*entries)[yProbe].Name] - if getMinorVersion(yProbeVersion).EQ(getMinorVersion(curVersion)) { - yProbe += 1 - } else { - break - } - } - // if yProbe crossed a threshold, the previous entry is the last of the previous Y-stream - preChangeIndex := yProbe - 1 - - if curEdge != yProbe { - if zmaxQueue != "" { - // add skips edge to allow skipping over y iterations within an x stream - (*entries)[preChangeIndex].Skips = append((*entries)[preChangeIndex].Skips, zmaxQueue) - (*entries)[preChangeIndex].Replaces = zmaxQueue - } - zmaxQueue = (*entries)[preChangeIndex].Name - } - for curEdge < preChangeIndex { - // add skips edges to y-1 from z < y - (*entries)[preChangeIndex].Skips = append((*entries)[preChangeIndex].Skips, (*entries)[curEdge].Name) - curEdge += 1 - } - curEdge += 1 - yProbe = curEdge + 1 - } - // since probe will always fail to pick up a y-change in the last item, test for it - if entryCount > 1 { - penultimateEntry := &(*entries)[len(*entries)-2] - ultimateEntry := &(*entries)[len(*entries)-1] - penultimateVersion := bundleVersions[penultimateEntry.Name] - ultimateVersion := bundleVersions[ultimateEntry.Name] - if ultimateVersion.Minor != penultimateVersion.Minor { - ultimateEntry.Replaces = penultimateEntry.Name - } - } - channels = append(channels, *channel) - } - - return channels -} - -func channelNameFromMinor(prefix channelArchetype, version semver.Version) string { - return fmt.Sprintf("%s-v%d.%d", prefix, version.Major, version.Minor) -} - -func channelNameFromMajor(prefix channelArchetype, version semver.Version) string { - return fmt.Sprintf("%s-v%d", prefix, version.Major) -} - -func newPackage(name string) *declcfg.Package { - return &declcfg.Package{ - Schema: "olm.package", - Name: name, - DefaultChannel: "", - } -} - -func newChannel(pkgName string, chName string) *declcfg.Channel { - return &declcfg.Channel{ - Schema: "olm.channel", - Name: string(chName), - Package: pkgName, - Entries: []declcfg.ChannelEntry{}, - } -} - -func combineConfigs(cfgs []declcfg.DeclarativeConfig) *declcfg.DeclarativeConfig { - out := &declcfg.DeclarativeConfig{} - for _, in := range cfgs { - out.Merge(&in) - } - return out -} - -func getMinorVersion(v semver.Version) semver.Version { - return semver.Version{ - Major: v.Major, - Minor: v.Minor, - } -} - -func getMajorVersion(v semver.Version) semver.Version { - return semver.Version{ - Major: v.Major, - } -} - -func withoutBuildMetadataConflict(versions *map[string]semver.Version) error { - errs := []error{} - - // using the stringified semver because the semver package generates deterministic representations, - // and because the semver.Version contains slice fields which make it unsuitable as a map key - // stringified-semver.Version ==> incidence count - seen := make(map[string]int) - for b := range *versions { - stripped := stripBuildMetadata((*versions)[b]) - if _, ok := seen[stripped]; !ok { - seen[stripped] = 1 - } else { - seen[stripped] = seen[stripped] + 1 - errs = append(errs, fmt.Errorf("bundle version %q cannot be compared to %q", (*versions)[b].String(), stripped)) - } - } - - if len(errs) != 0 { - return fmt.Errorf("encountered bundle versions which differ only by build metadata, which cannot be ordered: %v", errors.NewAggregate(errs)) - } - - return nil -} - -func validateVersions(versions *map[string]semver.Version) error { - // short-circuit if empty, since that is not an error - if len(*versions) == 0 { - return nil - } - return withoutBuildMetadataConflict(versions) -} - -// strips out the build metadata from a semver.Version and then stringifies it to make it suitable for collision detection -func stripBuildMetadata(v semver.Version) string { - v.Build = nil - return v.String() -} - -// prefer (in descending order of preference): -// - higher-rank archetype, -// - semver version, -// - a channel type matching the set preference, or -// - a 'better' (higher value) channel type -func (h *highwaterChannel) gt(ih *highwaterChannel, pref streamType) bool { - if channelPriorities[h.archetype] != channelPriorities[ih.archetype] { - return channelPriorities[h.archetype] > channelPriorities[ih.archetype] - } - if h.version.NE(ih.version) { - return h.version.GT(ih.version) - } - if h.kind != ih.kind { - if h.kind == pref { - return true - } - if ih.kind == pref { - return false - } - return h.kind.gt((*ih).kind) - } - return false -} - -func (t streamType) gt(in streamType) bool { - return streamTypePriorities[t] > streamTypePriorities[in] -} diff --git a/alpha/template/semver/semver_test.go b/alpha/template/semver/semver_test.go deleted file mode 100644 index 148290679..000000000 --- a/alpha/template/semver/semver_test.go +++ /dev/null @@ -1,715 +0,0 @@ -package semver - -import ( - "fmt" - "strings" - "testing" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func TestLinkChannels(t *testing.T) { - // type bundleVersions map[string]map[string]semver.Version // e.g. d["stable"]["example-operator.v1.0.0"] = 1.0.0 - channelOperatorVersions := bundleVersions{ - "stable": { - "a-v0.1.0": semver.MustParse("0.1.0"), - "a-v0.1.1": semver.MustParse("0.1.1"), - "a-v1.1.0": semver.MustParse("1.1.0"), - "a-v1.2.1": semver.MustParse("1.2.1"), - "a-v1.3.1": semver.MustParse("1.3.1"), - "a-v2.1.0": semver.MustParse("2.1.0"), - "a-v1.3.1-beta": semver.MustParse(("1.3.1-beta")), - "a-v2.1.1": semver.MustParse("2.1.1"), - "a-v2.3.1": semver.MustParse("2.3.1"), - "a-v2.3.2": semver.MustParse("2.3.2"), - "a-v3.1.0": semver.MustParse("3.1.0"), - "a-v3.1.1": semver.MustParse("3.1.1"), - "a-v1.3.1-alpha": semver.MustParse("1.3.1-alpha"), - "a-v1.4.1": semver.MustParse("1.4.1"), - "a-v1.4.1-beta1": semver.MustParse("1.4.1-beta1"), - "a-v1.4.1-beta2": semver.MustParse("1.4.1-beta2"), - }, - } - - majorGeneratedUnlinkedChannels := map[string]*declcfg.Channel{ - "stable-v0": { - Schema: "olm.channel", - Name: "stable-v0", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0"}, - {Name: "a-v0.1.1"}, - }, - }, - "stable-v1": { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0"}, - {Name: "a-v1.2.1"}, - {Name: "a-v1.3.1"}, - }, - }, - "stable-v2": { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0"}, - {Name: "a-v2.1.1"}, - {Name: "a-v2.3.1"}, - {Name: "a-v2.3.2"}, - }, - }, - } - - majorGeneratedUnlinkedChannelsLastXChange := map[string]*declcfg.Channel{ - "stable-v0": { - Schema: "olm.channel", - Name: "stable-v0", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0"}, - {Name: "a-v0.1.1"}, - }, - }, - "stable-v1": { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0"}, - {Name: "a-v1.2.1"}, - {Name: "a-v1.3.1"}, - }, - }, - "stable-v2": { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0"}, - }, - }, - } - - majorGeneratedUnlinkedChannelsLastArchChange := map[string]*declcfg.Channel{ - "candidate-v2": { - Schema: "olm.channel", - Name: "candidate-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.3.2"}, - }, - }, - "stable-v1": { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0"}, - {Name: "a-v1.2.1"}, - {Name: "a-v1.3.1"}, - }, - }, - "stable-v2": { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0"}, - {Name: "a-v2.1.1"}, - {Name: "a-v2.3.1"}, - }, - }, - } - - tests := []struct { - name string - unlinkedChannels map[string]*declcfg.Channel - generateMinorChannels bool - generateMajorChannels bool - out []declcfg.Channel - }{ - { - name: "No edges between successive major channels", - unlinkedChannels: majorGeneratedUnlinkedChannels, - generateMinorChannels: false, - generateMajorChannels: true, - out: []declcfg.Channel{ - { - Schema: "olm.channel", - Name: "stable-v0", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0", Replaces: ""}, - {Name: "a-v0.1.1", Replaces: "", Skips: []string{"a-v0.1.0"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0", Replaces: ""}, - {Name: "a-v1.2.1", Replaces: "a-v1.1.0", Skips: []string{"a-v1.1.0"}}, - {Name: "a-v1.3.1", Replaces: "a-v1.2.1", Skips: []string{"a-v1.2.1"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0", Replaces: ""}, - {Name: "a-v2.1.1", Replaces: "", Skips: []string{"a-v2.1.0"}}, - {Name: "a-v2.3.1", Replaces: ""}, - {Name: "a-v2.3.2", Replaces: "a-v2.1.1", Skips: []string{"a-v2.1.1", "a-v2.3.1"}}, - }, - }, - }, - }, - { - name: "No edges between successive major channels where last edge is X change", - unlinkedChannels: majorGeneratedUnlinkedChannelsLastXChange, - generateMinorChannels: false, - generateMajorChannels: true, - out: []declcfg.Channel{ - { - Schema: "olm.channel", - Name: "stable-v0", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0", Replaces: ""}, - {Name: "a-v0.1.1", Replaces: "", Skips: []string{"a-v0.1.0"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0", Replaces: ""}, - {Name: "a-v1.2.1", Replaces: "a-v1.1.0", Skips: []string{"a-v1.1.0"}}, - {Name: "a-v1.3.1", Replaces: "a-v1.2.1", Skips: []string{"a-v1.2.1"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0", Replaces: ""}, - }, - }, - }, - }, - { - name: "No edges between successive major channels where last edge is archetype change", - unlinkedChannels: majorGeneratedUnlinkedChannelsLastArchChange, - generateMinorChannels: false, - generateMajorChannels: true, - out: []declcfg.Channel{ - { - Schema: "olm.channel", - Name: "candidate-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.3.2", Replaces: ""}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0", Replaces: ""}, - {Name: "a-v1.2.1", Replaces: "a-v1.1.0", Skips: []string{"a-v1.1.0"}}, - {Name: "a-v1.3.1", Replaces: "a-v1.2.1", Skips: []string{"a-v1.2.1"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0", Replaces: ""}, - {Name: "a-v2.1.1", Replaces: "", Skips: []string{"a-v2.1.0"}}, - {Name: "a-v2.3.1", Replaces: "a-v2.1.1", Skips: []string{"a-v2.1.1"}}, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sv := &semverTemplate{pkg: "a", GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels} - require.ElementsMatch(t, tt.out, sv.linkChannels(tt.unlinkedChannels, &channelOperatorVersions)) - }) - } -} - -func TestGenerateChannels(t *testing.T) { - // type bundleVersions map[string]map[string]semver.Version // e.g. d["stable"]["example-operator.v1.0.0"] = 1.0.0 - channelOperatorVersions := bundleVersions{ - "stable": { - "a-v0.1.0": semver.MustParse("0.1.0"), - "a-v0.1.1": semver.MustParse("0.1.1"), - "a-v1.1.0": semver.MustParse("1.1.0"), - "a-v1.2.1": semver.MustParse("1.2.1"), - "a-v1.3.1": semver.MustParse("1.3.1"), - "a-v2.1.0": semver.MustParse("2.1.0"), - "a-v1.3.1-beta": semver.MustParse(("1.3.1-beta")), - "a-v2.1.1": semver.MustParse("2.1.1"), - "a-v2.3.1": semver.MustParse("2.3.1"), - "a-v2.3.2": semver.MustParse("2.3.2"), - "a-v3.1.0": semver.MustParse("3.1.0"), - "a-v3.1.1": semver.MustParse("3.1.1"), - "a-v1.3.1-alpha": semver.MustParse("1.3.1-alpha"), - "a-v1.4.1": semver.MustParse("1.4.1"), - "a-v1.4.1-beta1": semver.MustParse("1.4.1-beta1"), - "a-v1.4.1-beta2": semver.MustParse("1.4.1-beta2"), - }, - } - - majorLinkedChannels := []declcfg.Channel{ - { - Schema: "olm.channel", - Name: "stable-v0", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0", Replaces: ""}, - {Name: "a-v0.1.1", Replaces: "", Skips: []string{"a-v0.1.0"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0", Replaces: ""}, - {Name: "a-v1.2.1", Replaces: "a-v1.1.0", Skips: []string{"a-v1.1.0"}}, - {Name: "a-v1.3.1-alpha", Replaces: ""}, - {Name: "a-v1.3.1-beta", Replaces: ""}, - {Name: "a-v1.3.1", Replaces: "a-v1.2.1", Skips: []string{"a-v1.2.1", "a-v1.3.1-alpha", "a-v1.3.1-beta"}}, - {Name: "a-v1.4.1-beta1", Replaces: ""}, - {Name: "a-v1.4.1-beta2", Replaces: ""}, - {Name: "a-v1.4.1", Replaces: "a-v1.3.1", Skips: []string{"a-v1.3.1", "a-v1.4.1-beta1", "a-v1.4.1-beta2"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0", Replaces: ""}, - {Name: "a-v2.1.1", Replaces: "", Skips: []string{"a-v2.1.0"}}, - {Name: "a-v2.3.1", Replaces: ""}, - {Name: "a-v2.3.2", Replaces: "a-v2.1.1", Skips: []string{"a-v2.1.1", "a-v2.3.1"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v3", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v3.1.0", Replaces: ""}, - {Name: "a-v3.1.1", Replaces: "", Skips: []string{"a-v3.1.0"}}, - }, - }, - } - - minorLinkedChannels := []declcfg.Channel{ - { - Schema: "olm.channel", - Name: "stable-v0.1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v0.1.0", Replaces: ""}, - {Name: "a-v0.1.1", Replaces: "", Skips: []string{"a-v0.1.0"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1.1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.1.0", Replaces: ""}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1.2", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.2.1", Replaces: ""}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1.3", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.3.1-alpha", Replaces: ""}, - {Name: "a-v1.3.1-beta", Replaces: ""}, - {Name: "a-v1.3.1", Replaces: "", Skips: []string{"a-v1.3.1-alpha", "a-v1.3.1-beta"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v1.4", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v1.4.1-beta1", Replaces: ""}, - {Name: "a-v1.4.1-beta2", Replaces: ""}, - {Name: "a-v1.4.1", Replaces: "", Skips: []string{"a-v1.4.1-beta1", "a-v1.4.1-beta2"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2.1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.1.0", Replaces: ""}, - {Name: "a-v2.1.1", Replaces: "", Skips: []string{"a-v2.1.0"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v2.3", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v2.3.1", Replaces: ""}, - {Name: "a-v2.3.2", Replaces: "", Skips: []string{"a-v2.3.1"}}, - }, - }, - { - Schema: "olm.channel", - Name: "stable-v3.1", - Package: "a", - Entries: []declcfg.ChannelEntry{ - {Name: "a-v3.1.0", Replaces: ""}, - {Name: "a-v3.1.1", Replaces: "", Skips: []string{"a-v3.1.0"}}, - }, - }, - } - - var combinedLinkedChannels []declcfg.Channel - combinedLinkedChannels = append(combinedLinkedChannels, minorLinkedChannels...) - combinedLinkedChannels = append(combinedLinkedChannels, majorLinkedChannels...) - - tests := []struct { - name string - generateMinorChannels bool - generateMajorChannels bool - defaultChannel string - channelTypePreference streamType - out []declcfg.Channel - }{ - { - name: "Edges between minor channels", - generateMinorChannels: true, - generateMajorChannels: false, - defaultChannel: "stable-v3.1", - channelTypePreference: minorStreamType, - out: minorLinkedChannels, - }, - { - name: "No edges between major channels", - generateMinorChannels: false, - generateMajorChannels: true, - defaultChannel: "stable-v3", - channelTypePreference: majorStreamType, - out: majorLinkedChannels, - }, - { - name: "Preference for minor default channel", - generateMinorChannels: true, - generateMajorChannels: true, - defaultChannel: "stable-v3.1", - channelTypePreference: minorStreamType, - out: combinedLinkedChannels, - }, - { - name: "Preference for major default channel", - generateMinorChannels: true, - generateMajorChannels: true, - defaultChannel: "stable-v3", - channelTypePreference: majorStreamType, - out: combinedLinkedChannels, - }, - { - name: "Mismatch generate/preference minor/major default channel", - generateMinorChannels: true, - generateMajorChannels: false, - defaultChannel: "stable-v3.1", - channelTypePreference: majorStreamType, - out: minorLinkedChannels, - }, - { - name: "Mismatch generate/preference major/minor default channel", - generateMinorChannels: false, - generateMajorChannels: true, - defaultChannel: "stable-v3", - channelTypePreference: minorStreamType, - out: majorLinkedChannels, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sv := &semverTemplate{GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels, pkg: "a", DefaultChannelTypePreference: tt.channelTypePreference} - out := sv.generateChannels(&channelOperatorVersions) - require.ElementsMatch(t, tt.out, out) - require.Equal(t, tt.defaultChannel, sv.defaultChannel) - }) - } -} - -func TestGetVersionsFromStandardChannel(t *testing.T) { - tests := []struct { - name string - sv semverTemplate - outVersions bundleVersions - dc declcfg.DeclarativeConfig - }{ - { - name: "sunny day case", - sv: semverTemplate{ - Stable: semverTemplateChannelBundles{ - []semverTemplateBundleEntry{ - {Image: "repo/origin/a-v0.1.0"}, - {Image: "repo/origin/a-v0.1.1"}, - {Image: "repo/origin/a-v1.1.0"}, - {Image: "repo/origin/a-v1.2.1"}, - {Image: "repo/origin/a-v1.3.1"}, - {Image: "repo/origin/a-v2.1.0"}, - {Image: "repo/origin/a-v2.1.1"}, - {Image: "repo/origin/a-v2.3.1"}, - {Image: "repo/origin/a-v2.3.2"}, - {Image: "repo/origin/a-v1.3.1-alpha"}, - }, - }, - }, - outVersions: bundleVersions{ - "candidate": map[string]semver.Version{}, - "fast": map[string]semver.Version{}, - "stable": { - "a-v0.1.0": semver.MustParse("0.1.0"), - "a-v0.1.1": semver.MustParse("0.1.1"), - "a-v1.1.0": semver.MustParse("1.1.0"), - "a-v1.2.1": semver.MustParse("1.2.1"), - "a-v1.3.1": semver.MustParse("1.3.1"), - "a-v2.1.0": semver.MustParse("2.1.0"), - "a-v2.1.1": semver.MustParse("2.1.1"), - "a-v2.3.1": semver.MustParse("2.3.1"), - "a-v2.3.2": semver.MustParse("2.3.2"), - "a-v1.3.1-alpha": semver.MustParse("1.3.1-alpha"), - }, - }, - dc: declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "a", - }, - }, - Bundles: []declcfg.Bundle{ - {Schema: "olm.bundle", Image: "repo/origin/a-v0.1.0", Name: "a-v0.1.0", Properties: []property.Property{property.MustBuildPackage("a", "0.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v0.1.1", Name: "a-v0.1.1", Properties: []property.Property{property.MustBuildPackage("a", "0.1.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.1.0", Name: "a-v1.1.0", Properties: []property.Property{property.MustBuildPackage("a", "1.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.2.1", Name: "a-v1.2.1", Properties: []property.Property{property.MustBuildPackage("a", "1.2.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1-alpha", Name: "a-v1.3.1-alpha", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1-alpha")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1", Name: "a-v1.3.1", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.1.0", Name: "a-v2.1.0", Properties: []property.Property{property.MustBuildPackage("a", "2.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.1.1", Name: "a-v2.1.1", Properties: []property.Property{property.MustBuildPackage("a", "2.1.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.3.1", Name: "a-v2.3.1", Properties: []property.Property{property.MustBuildPackage("a", "2.3.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.3.2", Name: "a-v2.3.2", Properties: []property.Property{property.MustBuildPackage("a", "2.3.2")}}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - iosv := tt.sv - versions, err := iosv.getVersionsFromStandardChannels(&tt.dc) - require.NoError(t, err) - require.EqualValues(t, tt.outVersions, *versions) - require.EqualValues(t, "a", iosv.pkg) // verify that we learned the package name and stashed it in the receiver - }) - } - -} - -func TestBailOnVersionBuildMetadata(t *testing.T) { - sv := semverTemplate{ - Stable: semverTemplateChannelBundles{ - []semverTemplateBundleEntry{ - {Image: "repo/origin/a-v0.1.0"}, - {Image: "repo/origin/a-v0.1.1"}, - {Image: "repo/origin/a-v1.1.0"}, - {Image: "repo/origin/a-v1.2.1"}, - {Image: "repo/origin/a-v1.3.1"}, - {Image: "repo/origin/a-v2.1.0"}, - {Image: "repo/origin/a-v2.1.1"}, - {Image: "repo/origin/a-v2.3.1"}, - {Image: "repo/origin/a-v2.3.2"}, - {Image: "repo/origin/a-v1.3.1-alpha"}, - {Image: "repo/origin/a-v1.3.1-alpha+2001Jan21"}, - {Image: "repo/origin/a-v1.3.1-alpha+2003May06"}, - }, - }, - } - - dc := declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "a", - }, - }, - Bundles: []declcfg.Bundle{ - {Schema: "olm.bundle", Image: "repo/origin/a-v0.1.0", Name: "a-v0.1.0", Properties: []property.Property{property.MustBuildPackage("a", "0.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v0.1.1", Name: "a-v0.1.1", Properties: []property.Property{property.MustBuildPackage("a", "0.1.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.1.0", Name: "a-v1.1.0", Properties: []property.Property{property.MustBuildPackage("a", "1.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.2.1", Name: "a-v1.2.1", Properties: []property.Property{property.MustBuildPackage("a", "1.2.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1-alpha", Name: "a-v1.3.1-alpha", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1-alpha")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1-alpha+2001Jan21", Name: "a-v1.3.1-alpha+2001Jan21", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1-alpha+2001Jan21")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1", Name: "a-v1.3.1", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.1.0", Name: "a-v2.1.0", Properties: []property.Property{property.MustBuildPackage("a", "2.1.0")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.1.1", Name: "a-v2.1.1", Properties: []property.Property{property.MustBuildPackage("a", "2.1.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.3.1", Name: "a-v2.3.1", Properties: []property.Property{property.MustBuildPackage("a", "2.3.1")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v2.3.2", Name: "a-v2.3.2", Properties: []property.Property{property.MustBuildPackage("a", "2.3.2")}}, - {Schema: "olm.bundle", Image: "repo/origin/a-v1.3.1-alpha+2003May06", Name: "a-v1.3.1-alpha+2003May06", Properties: []property.Property{property.MustBuildPackage("a", "1.3.1-alpha+2003May06")}}, - }, - } - - t.Run("Abort on unorderable build metadata version data", func(t *testing.T) { - _, err := sv.getVersionsFromStandardChannels(&dc) - require.Error(t, err) - }) -} - -func TestReadFile(t *testing.T) { - - templateFstr := `--- -schema: olm.semver -generateMajorChannels: %s -generateMinorChannels: %s -defaultChannelTypePreference: %s -candidate: - bundles: - - image: quay.io/foo/olm:testoperator.v0.1.0 - - image: quay.io/foo/olm:testoperator.v0.1.1 - - image: quay.io/foo/olm:testoperator.v0.1.2 - - image: quay.io/foo/olm:testoperator.v0.1.3 - - image: quay.io/foo/olm:testoperator.v0.2.0 - - image: quay.io/foo/olm:testoperator.v0.2.1 - - image: quay.io/foo/olm:testoperator.v0.2.2 - - image: quay.io/foo/olm:testoperator.v0.3.0 - - image: quay.io/foo/olm:testoperator.v1.0.0 - - image: quay.io/foo/olm:testoperator.v1.0.1 - - image: quay.io/foo/olm:testoperator.v1.1.0 -fast: - bundles: - - image: quay.io/foo/olm:testoperator.v0.2.1 - - image: quay.io/foo/olm:testoperator.v0.2.2 - - image: quay.io/foo/olm:testoperator.v0.3.0 - - image: quay.io/foo/olm:testoperator.v1.0.1 - - image: quay.io/foo/olm:testoperator.v1.1.0 -stable: - bundles: - - image: quay.io/foo/olm:testoperator.v1.0.1 -` - - type testCase struct { - name string - input string - assertions func(*testing.T, *semverTemplate, error) - } - testCases := []testCase{ - { - name: "valid", - input: fmt.Sprintf(templateFstr, "true", "true", "minor"), - assertions: func(t *testing.T, template *semverTemplate, err error) { - require.NotNil(t, template) - require.NoError(t, err) - }, - }, - { - name: "unknown channel prefix", - input: `--- -schema: olm.semver -generateMajorChannels: true -generateMinorChannels: true -candidate: - bundles: - - image: quay.io/foo/olm:testoperator.v0.1.0 - - image: quay.io/foo/olm:testoperator.v0.1.1 - - image: quay.io/foo/olm:testoperator.v0.1.2 - - image: quay.io/foo/olm:testoperator.v0.1.3 - - image: quay.io/foo/olm:testoperator.v0.2.0 - - image: quay.io/foo/olm:testoperator.v0.2.1 - - image: quay.io/foo/olm:testoperator.v0.2.2 - - image: quay.io/foo/olm:testoperator.v0.3.0 - - image: quay.io/foo/olm:testoperator.v1.0.0 - - image: quay.io/foo/olm:testoperator.v1.0.1 - - image: quay.io/foo/olm:testoperator.v1.1.0 -fast: - bundles: - - image: quay.io/foo/olm:testoperator.v0.2.1 - - image: quay.io/foo/olm:testoperator.v0.2.2 - - image: quay.io/foo/olm:testoperator.v0.3.0 - - image: quay.io/foo/olm:testoperator.v1.0.1 - - image: quay.io/foo/olm:testoperator.v1.1.0 -stable: - bundles: - - image: quay.io/foo/olm:testoperator.v1.0.1 -invalid: - bundles: - - image: quay.io/foo/olm:testoperator.v1.0.1 -`, - assertions: func(t *testing.T, template *semverTemplate, err error) { - require.Nil(t, template) - require.EqualError(t, err, `error unmarshaling JSON: while decoding JSON: json: unknown field "invalid"`) - }, - }, - { - name: "generate/default mismatch, minor/major", - input: fmt.Sprintf(templateFstr, "true", "false", "minor"), - assertions: func(t *testing.T, template *semverTemplate, err error) { - require.Nil(t, template) - require.ErrorContains(t, err, "schema attribute mismatch") - }, - }, - { - name: "generate/default mismatch, major/minor", - input: fmt.Sprintf(templateFstr, "false", "true", "major"), - assertions: func(t *testing.T, template *semverTemplate, err error) { - require.Nil(t, template) - require.ErrorContains(t, err, "schema attribute mismatch") - }, - }, - { - name: "unknown defaultchanneltypepreference", - input: fmt.Sprintf(templateFstr, "false", "true", "foo"), - assertions: func(t *testing.T, template *semverTemplate, err error) { - require.Nil(t, template) - require.ErrorContains(t, err, "unknown DefaultChannelTypePreference") - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - sv, err := readFile(strings.NewReader(tc.input)) - tc.assertions(t, sv, err) - }) - } -} diff --git a/alpha/template/semver/types.go b/alpha/template/semver/types.go deleted file mode 100644 index 971718b34..000000000 --- a/alpha/template/semver/types.go +++ /dev/null @@ -1,82 +0,0 @@ -package semver - -import ( - "io" - - "github.com/blang/semver/v4" - "github.com/operator-framework/operator-registry/pkg/image" -) - -// data passed into this module externally -type Template struct { - Data io.Reader - Registry image.Registry -} - -// IO structs -- BEGIN -type semverTemplateBundleEntry struct { - Image string `json:"image,omitempty"` -} - -type semverTemplateChannelBundles struct { - Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` -} - -type semverTemplate struct { - Schema string `json:"schema"` - GenerateMajorChannels bool `json:"generateMajorChannels,omitempty"` - GenerateMinorChannels bool `json:"generateMinorChannels,omitempty"` - DefaultChannelTypePreference streamType `json:"defaultChannelTypePreference,omitempty"` - Candidate semverTemplateChannelBundles `json:"candidate,omitempty"` - Fast semverTemplateChannelBundles `json:"fast,omitempty"` - Stable semverTemplateChannelBundles `json:"stable,omitempty"` - - pkg string `json:"-"` // the derived package name - defaultChannel string `json:"-"` // detected "most stable" channel head -} - -// IO structs -- END - -const schema string = "olm.semver" - -// channel "archetypes", restricted in this iteration to just these -type channelArchetype string - -const ( - candidateChannelArchetype channelArchetype = "candidate" - fastChannelArchetype channelArchetype = "fast" - stableChannelArchetype channelArchetype = "stable" -) - -// mapping channel name --> stability, where higher values indicate greater stability -var channelPriorities = map[channelArchetype]int{candidateChannelArchetype: 0, fastChannelArchetype: 1, stableChannelArchetype: 2} - -// sorting capability for a slice according to the assigned channelPriorities -type byChannelPriority []channelArchetype - -func (b byChannelPriority) Len() int { return len(b) } -func (b byChannelPriority) Less(i, j int) bool { - return channelPriorities[b[i]] < channelPriorities[b[j]] -} -func (b byChannelPriority) Swap(i, j int) { b[i], b[j] = b[j], b[i] } - -type streamType string - -const defaultStreamType streamType = "" -const minorStreamType streamType = "minor" -const majorStreamType streamType = "major" - -// general preference for minor channels -var streamTypePriorities = map[streamType]int{minorStreamType: 2, majorStreamType: 1, defaultStreamType: 0} - -// map of archetypes --> bundles --> bundle-version from the input file -type bundleVersions map[channelArchetype]map[string]semver.Version // e.g. srcv["stable"]["example-operator.v1.0.0"] = 1.0.0 - -// the "high-water channel" struct functions as a freely-rising indicator of the "most stable" channel head, so we can use that -// later as the package's defaultChannel attribute -type highwaterChannel struct { - archetype channelArchetype - kind streamType - version semver.Version - name string -} diff --git a/cmd/opm/alpha/cmd.go b/cmd/opm/alpha/cmd.go index 202d9597f..1dc7a95b7 100644 --- a/cmd/opm/alpha/cmd.go +++ b/cmd/opm/alpha/cmd.go @@ -6,7 +6,6 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle" "github.com/operator-framework/operator-registry/cmd/opm/alpha/list" rendergraph "github.com/operator-framework/operator-registry/cmd/opm/alpha/render-graph" - "github.com/operator-framework/operator-registry/cmd/opm/alpha/template" ) func NewCmd(showAlphaHelp bool) *cobra.Command { @@ -25,7 +24,6 @@ func NewCmd(showAlphaHelp bool) *cobra.Command { bundle.NewCmd(), list.NewCmd(), rendergraph.NewCmd(), - template.NewCmd(), ) return runCmd } diff --git a/cmd/opm/alpha/template/basic.go b/cmd/opm/alpha/template/basic.go deleted file mode 100644 index 5d34ec2cd..000000000 --- a/cmd/opm/alpha/template/basic.go +++ /dev/null @@ -1,75 +0,0 @@ -package template - -import ( - "io" - "log" - "os" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/template/basic" - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" -) - -func newBasicTemplateCmd() *cobra.Command { - var ( - template basic.Template - output string - ) - cmd := &cobra.Command{ - Use: "basic basic-template-file", - Short: `Generate a file-based catalog from a single 'basic template' file -When FILE is '-' or not provided, the template is read from standard input`, - Long: `Generate a file-based catalog from a single 'basic template' file -When FILE is '-' or not provided, the template is read from standard input`, - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - // Handle different input argument types - // When no arguments or "-" is passed to the command, - // assume input is coming from stdin - // Otherwise open the file passed to the command - data, source, err := openFileOrStdin(cmd, args) - if err != nil { - log.Fatalf("unable to open %q: %v", source, err) - } - defer data.Close() - - var write func(declcfg.DeclarativeConfig, io.Writer) error - switch output { - case "yaml": - write = declcfg.WriteYAML - case "json": - write = declcfg.WriteJSON - default: - log.Fatalf("invalid --output value %q, expected (json|yaml)", output) - } - - // The bundle loading impl is somewhat verbose, even on the happy path, - // so discard all logrus default logger logs. Any important failures will be - // returned from template.Render and logged as fatal errors. - logrus.SetOutput(io.Discard) - - reg, err := util.CreateCLIRegistry(cmd) - if err != nil { - log.Fatalf("creating containerd registry: %v", err) - } - defer reg.Destroy() - - template.Registry = reg - - // only taking first file argument - cfg, err := template.Render(cmd.Context(), data) - if err != nil { - log.Fatal(err) - } - - if err := write(*cfg, os.Stdout); err != nil { - log.Fatal(err) - } - }, - } - cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") - return cmd -} diff --git a/cmd/opm/alpha/template/cmd.go b/cmd/opm/alpha/template/cmd.go deleted file mode 100644 index 1c435e6fa..000000000 --- a/cmd/opm/alpha/template/cmd.go +++ /dev/null @@ -1,30 +0,0 @@ -package template - -import ( - "io" - "os" - - "github.com/spf13/cobra" -) - -func NewCmd() *cobra.Command { - runCmd := &cobra.Command{ - Use: "render-template", - Short: "Render a catalog template type", - Args: cobra.NoArgs, - } - - runCmd.AddCommand(newBasicTemplateCmd()) - runCmd.AddCommand(newSemverTemplateCmd()) - runCmd.AddCommand(newCompositeTemplateCmd()) - - return runCmd -} - -func openFileOrStdin(cmd *cobra.Command, args []string) (io.ReadCloser, string, error) { - if len(args) == 0 || args[0] == "-" { - return io.NopCloser(cmd.InOrStdin()), "stdin", nil - } - reader, err := os.Open(args[0]) - return reader, args[0], err -} diff --git a/cmd/opm/alpha/template/composite.go b/cmd/opm/alpha/template/composite.go deleted file mode 100644 index 20c9a1e5c..000000000 --- a/cmd/opm/alpha/template/composite.go +++ /dev/null @@ -1,83 +0,0 @@ -package template - -import ( - "log" - "net/http" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/alpha/template/composite" - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" -) - -func newCompositeTemplateCmd() *cobra.Command { - var ( - output string - validate bool - compositeFile string - catalogFile string - ) - cmd := &cobra.Command{ - Use: "composite", - Short: `Generate file-based catalogs from a catalog configuration file -and a 'composite template' file`, - Long: `Generate file-based catalogs from a catalog configuration file -and a 'composite template' file`, - Args: cobra.MaximumNArgs(0), - Run: func(cmd *cobra.Command, args []string) { - - switch output { - case "yaml": - // do nothing - case "json": - // do nothing - default: - log.Fatalf("invalid --output value %q, expected (json|yaml)", output) - } - - reg, err := util.CreateCLIRegistry(cmd) - if err != nil { - log.Fatalf("creating containerd registry: %v", err) - } - defer reg.Destroy() - - // operator author's 'composite.yaml' file - compositeReader, err := os.Open(compositeFile) - if err != nil { - log.Fatalf("opening composite config file %q: %v", compositeFile, err) - } - defer compositeReader.Close() - - compositePath, err := filepath.Abs(filepath.Dir(compositeFile)) - if err != nil { - log.Fatalf("getting absolute path of composite config file %q: %v", compositeFile, err) - } - - // catalog maintainer's 'catalogs.yaml' file - tempCatalog, err := composite.FetchCatalogConfig(catalogFile, http.DefaultClient) - if err != nil { - log.Fatalf(err.Error()) - } - defer tempCatalog.Close() - - template := composite.NewTemplate( - composite.WithCatalogFile(tempCatalog), - composite.WithContributionFile(compositeReader, compositePath), - composite.WithOutputType(output), - composite.WithRegistry(reg), - ) - - err = template.Render(cmd.Context(), validate) - if err != nil { - log.Fatalf("rendering the composite template: %v", err) - } - }, - } - cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") - cmd.Flags().BoolVar(&validate, "validate", true, "whether or not the created FBC should be validated (i.e 'opm validate')") - cmd.Flags().StringVarP(&compositeFile, "composite-config", "c", "composite.yaml", "File to use as the composite configuration file") - cmd.Flags().StringVarP(&catalogFile, "catalog-config", "f", "catalogs.yaml", "File to use as the catalog configuration file") - return cmd -} diff --git a/cmd/opm/alpha/template/semver.go b/cmd/opm/alpha/template/semver.go deleted file mode 100644 index f67f9596c..000000000 --- a/cmd/opm/alpha/template/semver.go +++ /dev/null @@ -1,84 +0,0 @@ -package template - -import ( - "fmt" - "io" - "log" - "os" - - "github.com/sirupsen/logrus" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/template/semver" - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/spf13/cobra" -) - -func newSemverTemplateCmd() *cobra.Command { - output := "" - cmd := &cobra.Command{ - Use: "semver [FILE]", - Short: `Generate a file-based catalog from a single 'semver template' file -When FILE is '-' or not provided, the template is read from standard input`, - Long: `Generate a file-based catalog from a single 'semver template' file -When FILE is '-' or not provided, the template is read from standard input`, - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - // Handle different input argument types - // When no arguments or "-" is passed to the command, - // assume input is coming from stdin - // Otherwise open the file passed to the command - data, source, err := openFileOrStdin(cmd, args) - if err != nil { - return err - } - defer data.Close() - - var write func(declcfg.DeclarativeConfig, io.Writer) error - switch output { - case "json": - write = declcfg.WriteJSON - case "yaml": - write = declcfg.WriteYAML - case "mermaid": - write = func(cfg declcfg.DeclarativeConfig, writer io.Writer) error { - mermaidWriter := declcfg.NewMermaidWriter() - return mermaidWriter.WriteChannels(cfg, writer) - } - default: - return fmt.Errorf("invalid output format %q", output) - } - - // The bundle loading impl is somewhat verbose, even on the happy path, - // so discard all logrus default logger logs. Any important failures will be - // returned from template.Render and logged as fatal errors. - logrus.SetOutput(io.Discard) - - reg, err := util.CreateCLIRegistry(cmd) - if err != nil { - log.Fatalf("creating containerd registry: %v", err) - } - defer reg.Destroy() - - template := semver.Template{ - Data: data, - Registry: reg, - } - out, err := template.Render(cmd.Context()) - if err != nil { - log.Fatalf("semver %q: %v", source, err) - } - - if out != nil { - if err := write(*out, os.Stdout); err != nil { - log.Fatal(err) - } - } - - return nil - }, - } - - cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml|mermaid)") - return cmd -}