diff --git a/operator-registry.blacklist b/operator-registry.blacklist index b3e6ec7387..0ac251f2e0 100644 --- a/operator-registry.blacklist +++ b/operator-registry.blacklist @@ -1,2 +1,4 @@ 0b2f43f426f9d621cab7a5224fc260ae3265be1c eb2e6393796f397e93618c4aa5038f6a366cdb31 +6e014a3aa10f1754f7ac89ee7c0dd435f85b9d07 +25a478d9040b0f1fd50c5cbae570b013af01c73f diff --git a/staging/operator-registry/alpha/action/list_test.go b/staging/operator-registry/alpha/action/list_test.go index 87f53886e0..d4ca83977e 100644 --- a/staging/operator-registry/alpha/action/list_test.go +++ b/staging/operator-registry/alpha/action/list_test.go @@ -28,7 +28,7 @@ foo Foo Operator beta { name: "Error/UnknownIndex", list: ListPackages{IndexReference: "unknown-index"}, - expectedErr: `render reference "unknown-index": error resolving name : object required`, + expectedErr: `render reference "unknown-index": error resolving name for image ref unknown-index: object required`, }, } for _, s := range specs { @@ -79,7 +79,7 @@ foo stable foo.v0.2.0 { name: "Error/UnknownIndex", list: ListChannels{IndexReference: "unknown-index"}, - expectedErr: `render reference "unknown-index": error resolving name : object required`, + expectedErr: `render reference "unknown-index": error resolving name for image ref unknown-index: object required`, }, { name: "Error/UnknownPackage", @@ -138,7 +138,7 @@ foo stable foo.v0.2.0 foo.v0.1.0 foo.v0.1.1,foo.v0.1.2 <0.2.0 tes { name: "Error/UnknownIndex", list: ListBundles{IndexReference: "unknown-index"}, - expectedErr: `render reference "unknown-index": error resolving name : object required`, + expectedErr: `render reference "unknown-index": error resolving name for image ref unknown-index: object required`, }, { name: "Error/UnknownPackage", diff --git a/staging/operator-registry/alpha/declcfg/load.go b/staging/operator-registry/alpha/declcfg/load.go index 65c289780e..98b25da02a 100644 --- a/staging/operator-registry/alpha/declcfg/load.go +++ b/staging/operator-registry/alpha/declcfg/load.go @@ -1,6 +1,7 @@ package declcfg import ( + "bytes" "encoding/json" "errors" "fmt" @@ -135,7 +136,7 @@ func LoadReader(r io.Reader) (*DeclarativeConfig, error) { var in Meta if err := json.Unmarshal(doc, &in); err != nil { - return nil, err + return nil, fmt.Errorf("unmarshal error: %s", resolveUnmarshalErr(doc, err)) } switch in.Schema { @@ -186,3 +187,47 @@ func LoadFile(root fs.FS, path string) (*DeclarativeConfig, error) { return cfg, nil } + +func resolveUnmarshalErr(data []byte, err error) string { + var te *json.UnmarshalTypeError + if errors.As(err, &te) { + return formatUnmarshallErrorString(data, te.Error(), te.Offset) + } + var se *json.SyntaxError + if errors.As(err, &se) { + return formatUnmarshallErrorString(data, se.Error(), se.Offset) + } + return err.Error() +} + +func formatUnmarshallErrorString(data []byte, errmsg string, offset int64) string { + sb := new(strings.Builder) + _, _ = sb.WriteString(fmt.Sprintf("%s at offset %d (indicated by <==)\n ", errmsg, offset)) + // attempt to present the erroneous JSON in indented, human-readable format + // errors result in presenting the original, unformatted output + var pretty bytes.Buffer + err := json.Indent(&pretty, data, "", " ") + if err == nil { + pString := pretty.String() + // calc the prettified string offset which correlates to the original string offset + var pOffset, origOffset int64 + origOffset = 0 + for origOffset = 0; origOffset < offset; { + pOffset++ + if pString[pOffset] != '\n' && pString[pOffset] != ' ' { + origOffset++ + } + } + _, _ = sb.WriteString(pString[:pOffset]) + _, _ = sb.WriteString(" <== ") + _, _ = sb.WriteString(pString[pOffset:]) + } else { + for i := int64(0); i < offset; i++ { + _ = sb.WriteByte(data[i]) + } + _, _ = sb.WriteString(" <== ") + _, _ = sb.Write(data[offset:]) + } + + return sb.String() +} diff --git a/staging/operator-registry/alpha/veneer/basic/basic.go b/staging/operator-registry/alpha/template/basic/basic.go similarity index 70% rename from staging/operator-registry/alpha/veneer/basic/basic.go rename to staging/operator-registry/alpha/template/basic/basic.go index 26c2788b57..18566fbcf2 100644 --- a/staging/operator-registry/alpha/veneer/basic/basic.go +++ b/staging/operator-registry/alpha/template/basic/basic.go @@ -10,11 +10,11 @@ import ( "github.com/operator-framework/operator-registry/pkg/image" ) -type Veneer struct { +type Template struct { Registry image.Registry } -func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { +func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { cfg, err := declcfg.LoadReader(reader) if err != nil { return cfg, err @@ -23,13 +23,13 @@ func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.Declarat 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: v.Registry, + Registry: t.Registry, AllowedRefMask: action.RefBundleImage, } for _, b := range cfg.Bundles { - if !isBundleVeneer(&b) { - return nil, fmt.Errorf("unexpected fields present in basic veneer bundle") + if !isBundleTemplate(&b) { + return nil, fmt.Errorf("unexpected fields present in basic template bundle") } r.Refs = []string{b.Image} contributor, err := r.Run(ctx) @@ -43,8 +43,8 @@ func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.Declarat return cfg, nil } -// isBundleVeneer identifies a Bundle veneer source as having a Schema and Image defined +// isBundleTemplate identifies a Bundle template source as having a Schema and Image defined // but no Properties, RelatedImages or Package defined -func isBundleVeneer(b *declcfg.Bundle) bool { +func isBundleTemplate(b *declcfg.Bundle) bool { return b.Schema != "" && b.Image != "" && b.Package == "" && len(b.Properties) == 0 && len(b.RelatedImages) == 0 } diff --git a/staging/operator-registry/alpha/template/composite/builder.go b/staging/operator-registry/alpha/template/composite/builder.go new file mode 100644 index 0000000000..3d1f7af95d --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/builder.go @@ -0,0 +1,349 @@ +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 ContainerConfig struct { + ContainerTool string + BaseImage string + WorkingDir string +} + +type BuilderConfig struct { + ContainerCfg ContainerConfig + OutputType string + InputDirectory string +} + +type Builder interface { + Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error + Validate(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 { + 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.ContainerCfg.WorkingDir, dir, basicConfig.Output) + + return build(dcfg, destPath, bb.builderCfg.OutputType) +} + +func (bb *BasicBuilder) Validate(dir string) error { + return validate(bb.builderCfg.ContainerCfg, 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 { + 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.ContainerCfg.WorkingDir, dir, semverConfig.Output) + + return build(dcfg, destPath, sb.builderCfg.OutputType) +} + +func (sb *SemverBuilder) Validate(dir string) error { + return validate(sb.builderCfg.ContainerCfg, 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 { + return fmt.Errorf("error reading raw input file: %s, %v", rawConfig.Input, 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.ContainerCfg.WorkingDir, dir, rawConfig.Output) + + return build(dcfg, destPath, rb.builderCfg.OutputType) +} + +func (rb *RawBuilder) Validate(dir string) error { + return validate(rb.builderCfg.ContainerCfg, 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...) + cmd.Dir = cb.builderCfg.ContainerCfg.WorkingDir + + // 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", cmd.String(), err) + } + + 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.ContainerCfg.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(dir string) error { + return validate(cb.builderCfg.ContainerCfg, 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(containerCfg ContainerConfig, dir string) error { + + path := path.Join(containerCfg.WorkingDir, dir) + s, err := os.Stat(path) + if err != nil { + return fmt.Errorf("directory not found. validation path needs to be composed of ContainerConfig.WorkingDir+Component[].Destination.Path: %q: %v", path, err) + } + if !s.IsDir() { + return fmt.Errorf("%q is not a directory", path) + } + + if err := config.Validate(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/staging/operator-registry/alpha/template/composite/builder_test.go b/staging/operator-registry/alpha/template/composite/builder_test.go new file mode 100644 index 0000000000..af7850b076 --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/builder_test.go @@ -0,0 +1,1725 @@ +package composite + +import ( + "context" + "fmt" + "io" + "os" + "path" + "testing" + + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" + "github.com/stretchr/testify/require" +) + +// 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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)") + }, + }, + } + + for i, tc := range testCases { + tc.basicBuilder.builderCfg.InputDirectory = testDir + 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(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.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 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.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 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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, string(fileData), semverBuiltFbcYaml) + }, + validateAssertions: func(t *testing.T, validateErr error) { + require.NoError(t, validateErr) + }, + }, + { + name: "successful semver build json output", + validate: true, + semverBuilder: NewSemverBuilder(BuilderConfig{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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, string(fileData), semverBuiltFbcJson) + }, + validateAssertions: func(t *testing.T, validateErr error) { + require.NoError(t, validateErr) + }, + }, + { + name: "invalid template configuration", + validate: false, + semverBuilder: NewSemverBuilder(BuilderConfig{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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, + buildErr.Error(), + "semver template configuration is invalid: semver template config must have a non-empty output (templateDefinition.config.output)") + }, + }, + { + name: "template config has empty input & output", + validate: false, + semverBuilder: NewSemverBuilder(BuilderConfig{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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, + buildErr.Error(), + "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)") + }, + }, + } + + for i, tc := range testCases { + tc.semverBuilder.builderCfg.InputDirectory = testDir + 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(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 +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.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 semverBuiltFbcJson = `{ + "schema": "olm.package", + "name": "webhook-operator", + "defaultChannel": "stable-v0" +} +{ + "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.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 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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)") + }, + }, + } + + for i, tc := range testCases { + tc.rawBuilder.builderCfg.InputDirectory = testDir + 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(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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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{ + ContainerCfg: ContainerConfig{ + ContainerTool: "docker", + BaseImage: "quay.io/operator-framework/opm:latest", + 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 { + tc.customBuilder.builderCfg.InputDirectory = testDir + 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(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(ContainerConfig{}, "") + require.Error(t, err) + require.Contains(t, err.Error(), "no such file or directory") +} diff --git a/staging/operator-registry/alpha/template/composite/composite.go b/staging/operator-registry/alpha/template/composite/composite.go new file mode 100644 index 0000000000..422017c974 --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/composite.go @@ -0,0 +1,50 @@ +package composite + +import ( + "context" + "fmt" + + "github.com/operator-framework/operator-registry/pkg/image" +) + +type BuilderMap map[string]Builder + +type CatalogBuilderMap map[string]BuilderMap + +type Template struct { + CatalogBuilders CatalogBuilderMap + Registry image.Registry +} + +// TODO(everettraven): do we need the context here? If so, how should it be used? +func (t *Template) Render(ctx context.Context, config *CompositeConfig, validate bool) error { + // TODO(everettraven): should we return aggregated errors? + for _, component := range config.Components { + if builderMap, ok := t.CatalogBuilders[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(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 t.CatalogBuilders { + 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 +} diff --git a/staging/operator-registry/alpha/template/composite/composite_test.go b/staging/operator-registry/alpha/template/composite/composite_test.go new file mode 100644 index 0000000000..c2b28fac7b --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/composite_test.go @@ -0,0 +1,258 @@ +package composite + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/stretchr/testify/require" +) + +type TestBuilder struct { + buildError bool + validateError bool +} + +const buildErr = "TestBuilder Build() error" +const validateErr = "TestBuilder Validate() error" + +var _ Builder = &TestBuilder{} + +func (tb *TestBuilder) Build(ctx context.Context, reg image.Registry, dir string, vd TemplateDefinition) error { + if tb.buildError { + return errors.New(buildErr) + } + return nil +} + +func (tb *TestBuilder) Validate(dir string) error { + if tb.validateError { + return errors.New(validateErr) + } + return nil +} + +func TestCompositeRender(t *testing.T) { + type testCase struct { + name string + compositeTemplate Template + compositeCfg CompositeConfig + validate bool + assertions func(t *testing.T, err error) + } + + testCases := []testCase{ + { + name: "successful render", + validate: true, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "testcatalog", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.test", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + assertions: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + { + name: "component not in catalog config", + validate: true, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "invalid", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.test", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + 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", "invalid", []string{"testcatalog"}) + require.Equal(t, expectedErr, err.Error()) + }, + }, + { + name: "builder not in catalog config", + validate: true, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "testcatalog", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.invalid", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + assertions: func(t *testing.T, err error) { + require.Error(t, err) + expectedErr := fmt.Sprintf("building component %q: no builder found for template schema %q", "testcatalog", "olm.builder.invalid") + require.Equal(t, expectedErr, err.Error()) + }, + }, + { + name: "build step error", + validate: true, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{buildError: true}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "testcatalog", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.test", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + assertions: func(t *testing.T, err error) { + require.Error(t, err) + expectedErr := fmt.Sprintf("building component %q: %s", "testcatalog", buildErr) + require.Equal(t, expectedErr, err.Error()) + }, + }, + { + name: "validate step error", + validate: true, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{validateError: true}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "testcatalog", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.test", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + assertions: func(t *testing.T, err error) { + require.Error(t, err) + expectedErr := fmt.Sprintf("validating component %q: %s", "testcatalog", validateErr) + require.Equal(t, expectedErr, err.Error()) + }, + }, + { + name: "validation step skipped", + validate: false, + compositeTemplate: Template{ + CatalogBuilders: CatalogBuilderMap{ + "testcatalog": BuilderMap{ + "olm.builder.test": &TestBuilder{validateError: true}, + }, + }, + }, + compositeCfg: CompositeConfig{ + Schema: CompositeSchema, + Components: []Component{ + { + Name: "testcatalog", + Destination: ComponentDestination{ + Path: "testcatalog/mypackage", + }, + Strategy: BuildStrategy{ + Name: "testbuild", + Template: TemplateDefinition{ + Schema: "olm.builder.test", + Config: json.RawMessage{}, + }, + }, + }, + }, + }, + assertions: func(t *testing.T, err error) { + // the validate step would error but since + // we are skipping it we expect no error to occur + require.NoError(t, err) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.compositeTemplate.Render(context.Background(), &tc.compositeCfg, tc.validate) + tc.assertions(t, err) + }) + } +} diff --git a/staging/operator-registry/alpha/template/composite/config.go b/staging/operator-registry/alpha/template/composite/config.go new file mode 100644 index 0000000000..70a480d4e8 --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/config.go @@ -0,0 +1,42 @@ +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/staging/operator-registry/alpha/template/composite/types.go b/staging/operator-registry/alpha/template/composite/types.go new file mode 100644 index 0000000000..5295a5ddfd --- /dev/null +++ b/staging/operator-registry/alpha/template/composite/types.go @@ -0,0 +1,29 @@ +package composite + +import "encoding/json" + +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 +} diff --git a/staging/operator-registry/alpha/veneer/semver/README.md b/staging/operator-registry/alpha/template/semver/README.md similarity index 70% rename from staging/operator-registry/alpha/veneer/semver/README.md rename to staging/operator-registry/alpha/template/semver/README.md index e69726c487..2bab90c1c0 100644 --- a/staging/operator-registry/alpha/veneer/semver/README.md +++ b/staging/operator-registry/alpha/template/semver/README.md @@ -1,23 +1,23 @@ -## Semver Veneer: +## Semver Template: -Since a `veneer` is identified as an input schema which may be processed to generate a valid FBC, we can define a `semver veneer` as a schema which uses channel conventions to facilitate the auto-generation of channels along `semver` delimiters. +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, it cannot be used in any bundle included in the `semver veneer` and will result in a fatal error.] +[**DISCLAIMER:** since version build metadata [MUST be ignored when determining version precedence](https://semver.org) when using semver, it cannot be used in any bundle included in the `semver template` and will result in a fatal error.] ### Schema Goals -The `semver veneer` must have: +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 veneer entities +The resulting FBC must clearly indicate how generated channels relate to template entities ### Schema Anatomy -For convenience and simplicity, this veneer 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. +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 veneer will generate X-stream or Y-stream channels (attributes can be set independently). If omitted, only minor (Y-stream) channels will be generated. +`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. @@ -54,12 +54,12 @@ In this example, `Candidate` has the entire version range of bundles, `Fast` ha ### CLI Tool Usage ``` -% ./bin/opm alpha render-veneer semver -h -Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input +% ./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-veneer semver [FILE] [flags] + opm alpha render-template semver [FILE] [flags] Flags: -h, --help help for semver @@ -73,19 +73,19 @@ Global Flags: Example command usage: ``` # Example with file argument passed in -opm alpha render-veneer semver infile.semver.veneer.yaml +opm alpha render-template semver infile.semver.template.yaml # Example with no file argument passed in -opm alpha render-veneer semver -o yaml < infile.semver.veneer.yaml > outfile.yaml +opm alpha render-template semver -o yaml < infile.semver.template.yaml > outfile.yaml # Example with "-" as the file argument passed in -cat infile.semver.veneer.yaml | opm alpha render-veneer semver -o mermaid - +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 veneer attribute `GenerateMajorChannels: true` resulting major channels from the command are (skipping the rendered bundle image output): +With the template attribute `GenerateMajorChannels: true` resulting major channels from the command are (skipping the rendered bundle image output): ```yaml --- defaultChannel: stable-v1 @@ -151,10 +151,10 @@ package: testoperator schema: olm.channel ``` -We generated a channel for each veneer 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. +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 veneer attribute `GenerateMinorChannels: true` and running the command again (again skipping rendered bundle image output) we get a bunch more channels: +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 @@ -245,7 +245,7 @@ package: testoperator schema: olm.channel ``` -Here, a channel is generated for each veneer channel which differs by minor version, and each channel has a `replaces` edge from the predecessor channel to the next-lesser minor bundle version. 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. +Here, a channel is generated for each template channel which differs by minor version, and each channel has a `replaces` edge from the predecessor channel to the next-lesser minor bundle version. 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 diff --git a/staging/operator-registry/alpha/template/semver/major-version-demo.gif b/staging/operator-registry/alpha/template/semver/major-version-demo.gif new file mode 100755 index 0000000000..244ecd6dbb Binary files /dev/null and b/staging/operator-registry/alpha/template/semver/major-version-demo.gif differ diff --git a/staging/operator-registry/alpha/template/semver/minor-version-demo.gif b/staging/operator-registry/alpha/template/semver/minor-version-demo.gif new file mode 100755 index 0000000000..af0fea0b36 Binary files /dev/null and b/staging/operator-registry/alpha/template/semver/minor-version-demo.gif differ diff --git a/staging/operator-registry/alpha/veneer/semver/semver.go b/staging/operator-registry/alpha/template/semver/semver.go similarity index 89% rename from staging/operator-registry/alpha/veneer/semver/semver.go rename to staging/operator-registry/alpha/template/semver/semver.go index 00edcc64f4..0bb623cd23 100644 --- a/staging/operator-registry/alpha/veneer/semver/semver.go +++ b/staging/operator-registry/alpha/template/semver/semver.go @@ -17,27 +17,27 @@ import ( ) // data passed into this module externally -type Veneer struct { +type Template struct { Data io.Reader Registry image.Registry } // IO structs -- BEGIN -type semverVeneerBundleEntry struct { +type semverTemplateBundleEntry struct { Image string `json:"image,omitempty"` } type candidateBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } type fastBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } type stableBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } -type semverVeneer struct { +type semverTemplate struct { Schema string `json:"schema"` GenerateMajorChannels bool `json:"generateMajorChannels,omitempty"` GenerateMinorChannels bool `json:"generateMinorChannels,omitempty"` @@ -75,10 +75,10 @@ func (b byChannelPriority) Swap(i, j int) { b[i], b[j] = b[j], b[i] } // channels --> bundles --> version type semverRenderedChannelVersions map[string]map[string]semver.Version // e.g. d["stable-v1"]["example-operator/v1.0.0"] = 1.0.0 -func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) { +func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) { var out declcfg.DeclarativeConfig - sv, err := readFile(v.Data) + sv, err := readFile(t.Data) if err != nil { return nil, fmt.Errorf("semver-render: unable to read file: %v", err) } @@ -94,7 +94,7 @@ func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) r := action.Render{ AllowedRefMask: action.RefBundleImage, Refs: []string{b}, - Registry: v.Registry, + Registry: t.Registry, } c, err := r.Run(ctx) if err != nil { @@ -120,7 +120,7 @@ func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) return &out, nil } -func buildBundleList(bundles *[]semverVeneerBundleEntry, dict *map[string]struct{}) { +func buildBundleList(bundles *[]semverTemplateBundleEntry, dict *map[string]struct{}) { for _, b := range *bundles { if _, ok := (*dict)[b.Image]; !ok { (*dict)[b.Image] = struct{}{} @@ -128,25 +128,25 @@ func buildBundleList(bundles *[]semverVeneerBundleEntry, dict *map[string]struct } } -func readFile(data io.Reader) (*semverVeneer, error) { - fileData, err := io.ReadAll(data) +func readFile(reader io.Reader) (*semverTemplate, error) { + fileData, err := io.ReadAll(reader) if err != nil { return nil, err } // default behavior is to generate only minor channels and to use skips over replaces - sv := semverVeneer{ + sv := semverTemplate{ GenerateMajorChannels: false, GenerateMinorChannels: true, AvoidSkipPatch: false, } - if err := yaml.Unmarshal(fileData, &sv); err != nil { + if err := yaml.UnmarshalStrict(fileData, &sv); err != nil { return nil, err } return &sv, nil } -func (sv *semverVeneer) getVersionsFromStandardChannels(cfg *declcfg.DeclarativeConfig) (*semverRenderedChannelVersions, error) { +func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.DeclarativeConfig) (*semverRenderedChannelVersions, error) { versions := semverRenderedChannelVersions{} bdm, err := sv.getVersionsFromChannel(sv.Candidate.Bundles, cfg) @@ -179,15 +179,15 @@ func (sv *semverVeneer) getVersionsFromStandardChannels(cfg *declcfg.Declarative return &versions, nil } -func (sv *semverVeneer) getVersionsFromChannel(semverBundles []semverVeneerBundleEntry, cfg *declcfg.DeclarativeConfig) (map[string]semver.Version, error) { +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 veneer, to: + // 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 veneer bundles to rendered bundles in `entries` which is accumulated by the caller + // - 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 veneer is present in the successfully-rendered bundles + // 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 { @@ -247,11 +247,11 @@ func (h *highwaterChannel) gt(ih *highwaterChannel) bool { return (channelPriorities[h.kind] > channelPriorities[ih.kind]) || (h.version.GT(ih.version)) } -// generates an unlinked channel for each channel as per the input veneer config (major || minor), then link up the edges of the set of channels so that: +// 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 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 minor versions of the bundle enumerated in the veneer. +// - within the same minor version (Y-stream), the head of the channel should have a 'skips' encompassing all lesser minor 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 *semverVeneer) generateChannels(semverChannels *semverRenderedChannelVersions) []declcfg.Channel { +func (sv *semverTemplate) generateChannels(semverChannels *semverRenderedChannelVersions) []declcfg.Channel { outChannels := []declcfg.Channel{} // sort the channelkinds in ascending order so we can traverse the bundles in order of @@ -334,7 +334,7 @@ func (sv *semverVeneer) generateChannels(semverChannels *semverRenderedChannelVe } // all channels that come to linkChannels MUST have the same prefix. This adds replaces edges of minor versions of the largest major version. -func (sv *semverVeneer) linkChannels(unlinkedChannels map[string]*declcfg.Channel, pkg string, semverChannels *semverRenderedChannelVersions, channelMapping *map[string]string) []declcfg.Channel { +func (sv *semverTemplate) linkChannels(unlinkedChannels map[string]*declcfg.Channel, pkg string, semverChannels *semverRenderedChannelVersions, channelMapping *map[string]string) []declcfg.Channel { channels := []declcfg.Channel{} for channelName, channel := range unlinkedChannels { diff --git a/staging/operator-registry/alpha/veneer/semver/semver_test.go b/staging/operator-registry/alpha/template/semver/semver_test.go similarity index 84% rename from staging/operator-registry/alpha/veneer/semver/semver_test.go rename to staging/operator-registry/alpha/template/semver/semver_test.go index 68b2b29884..fdd0e5e85f 100644 --- a/staging/operator-registry/alpha/veneer/semver/semver_test.go +++ b/staging/operator-registry/alpha/template/semver/semver_test.go @@ -1,6 +1,7 @@ package semver import ( + "strings" "testing" "github.com/blang/semver/v4" @@ -366,7 +367,7 @@ func TestLinkChannels(t *testing.T) { for c, e := range tt.unlinkedChannels { unlinkedChannels[c] = e } - sv := &semverVeneer{AvoidSkipPatch: tt.avoidSkipPatch, GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels} + sv := &semverTemplate{AvoidSkipPatch: tt.avoidSkipPatch, GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels} require.ElementsMatch(t, tt.out, sv.linkChannels(unlinkedChannels, "a", &channelOperatorVersions, &channelNameToKind)) }) } @@ -486,7 +487,7 @@ func TestGenerateChannels(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sv := &semverVeneer{AvoidSkipPatch: tt.avoidSkipPatch, GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels, pkg: "a"} + sv := &semverTemplate{AvoidSkipPatch: tt.avoidSkipPatch, GenerateMajorChannels: tt.generateMajorChannels, GenerateMinorChannels: tt.generateMinorChannels, pkg: "a"} require.ElementsMatch(t, tt.out, sv.generateChannels(&channelOperatorVersions)) }) } @@ -495,15 +496,15 @@ func TestGenerateChannels(t *testing.T) { func TestGetVersionsFromStandardChannel(t *testing.T) { tests := []struct { name string - sv semverVeneer + sv semverTemplate outVersions semverRenderedChannelVersions dc declcfg.DeclarativeConfig }{ { name: "sunny day case", - sv: semverVeneer{ + sv: semverTemplate{ Stable: stableBundles{ - []semverVeneerBundleEntry{ + []semverTemplateBundleEntry{ {Image: "repo/origin/a-v0.1.0"}, {Image: "repo/origin/a-v0.1.1"}, {Image: "repo/origin/a-v1.1.0"}, @@ -568,9 +569,9 @@ func TestGetVersionsFromStandardChannel(t *testing.T) { } func TestBailOnVersionBuildMetadata(t *testing.T) { - sv := semverVeneer{ + sv := semverTemplate{ Stable: stableBundles{ - []semverVeneerBundleEntry{ + []semverTemplateBundleEntry{ {Image: "repo/origin/a-v0.1.0"}, {Image: "repo/origin/a-v0.1.1"}, {Image: "repo/origin/a-v1.1.0"}, @@ -615,3 +616,93 @@ func TestBailOnVersionBuildMetadata(t *testing.T) { require.Error(t, err) }) } + +func TestReadFile(t *testing.T) { + type testCase struct { + name string + input string + assertions func(*testing.T, *semverTemplate, error) + } + testCases := []testCase{ + { + name: "valid", + 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 +`, + 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"`) + }, + }, + } + + 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/staging/operator-registry/alpha/veneer/semver/major-version-demo.gif b/staging/operator-registry/alpha/veneer/semver/major-version-demo.gif deleted file mode 100644 index bf2faa3795..0000000000 Binary files a/staging/operator-registry/alpha/veneer/semver/major-version-demo.gif and /dev/null differ diff --git a/staging/operator-registry/alpha/veneer/semver/minor-version-demo.gif b/staging/operator-registry/alpha/veneer/semver/minor-version-demo.gif deleted file mode 100644 index 3644292b92..0000000000 Binary files a/staging/operator-registry/alpha/veneer/semver/minor-version-demo.gif and /dev/null differ diff --git a/staging/operator-registry/cmd/opm/alpha/cmd.go b/staging/operator-registry/cmd/opm/alpha/cmd.go index 74e88edbe7..55511cb2ea 100644 --- a/staging/operator-registry/cmd/opm/alpha/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/cmd.go @@ -6,7 +6,7 @@ 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/veneer" + "github.com/operator-framework/operator-registry/cmd/opm/alpha/template" ) func NewCmd() *cobra.Command { @@ -15,13 +15,14 @@ func NewCmd() *cobra.Command { Use: "alpha", Short: "Run an alpha subcommand", Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy } runCmd.AddCommand( bundle.NewCmd(), list.NewCmd(), rendergraph.NewCmd(), - veneer.NewCmd(), + template.NewCmd(), ) return runCmd } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/basic.go b/staging/operator-registry/cmd/opm/alpha/template/basic.go similarity index 70% rename from vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/basic.go rename to staging/operator-registry/cmd/opm/alpha/template/basic.go index 40311c0eb2..2510a4fa74 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/basic.go +++ b/staging/operator-registry/cmd/opm/alpha/template/basic.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "io" @@ -10,21 +10,21 @@ import ( "github.com/spf13/cobra" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/veneer/basic" + "github.com/operator-framework/operator-registry/alpha/template/basic" "github.com/operator-framework/operator-registry/cmd/opm/internal/util" ) -func newBasicVeneerRenderCmd() *cobra.Command { +func newBasicTemplateCmd() *cobra.Command { var ( - veneer basic.Veneer - output string + template basic.Template + output string ) cmd := &cobra.Command{ - Use: "basic basic-veneer-file", - Short: `Generate a file-based catalog from a single 'basic veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, - Long: `Generate a file-based catalog from a single 'basic veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, + 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 @@ -49,7 +49,7 @@ When FILE is '-' or not provided, the veneer is read from standard input`, // 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 veneer.Render and logged as fatal errors. + // returned from template.Render and logged as fatal errors. logrus.SetOutput(ioutil.Discard) reg, err := util.CreateCLIRegistry(cmd) @@ -58,10 +58,10 @@ When FILE is '-' or not provided, the veneer is read from standard input`, } defer reg.Destroy() - veneer.Registry = reg + template.Registry = reg // only taking first file argument - cfg, err := veneer.Render(cmd.Context(), data) + cfg, err := template.Render(cmd.Context(), data) if err != nil { log.Fatal(err) } diff --git a/staging/operator-registry/cmd/opm/alpha/veneer/cmd.go b/staging/operator-registry/cmd/opm/alpha/template/cmd.go similarity index 64% rename from staging/operator-registry/cmd/opm/alpha/veneer/cmd.go rename to staging/operator-registry/cmd/opm/alpha/template/cmd.go index 2bdec4e1f7..1c435e6fa2 100644 --- a/staging/operator-registry/cmd/opm/alpha/veneer/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/template/cmd.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "io" @@ -9,13 +9,14 @@ import ( func NewCmd() *cobra.Command { runCmd := &cobra.Command{ - Use: "render-veneer", - Short: "Render a veneer type", + Use: "render-template", + Short: "Render a catalog template type", Args: cobra.NoArgs, } - runCmd.AddCommand(newBasicVeneerRenderCmd()) - runCmd.AddCommand(newSemverCmd()) + runCmd.AddCommand(newBasicTemplateCmd()) + runCmd.AddCommand(newSemverTemplateCmd()) + runCmd.AddCommand(newCompositeTemplateCmd()) return runCmd } diff --git a/staging/operator-registry/cmd/opm/alpha/template/composite.go b/staging/operator-registry/cmd/opm/alpha/template/composite.go new file mode 100644 index 0000000000..1d8b9df0cb --- /dev/null +++ b/staging/operator-registry/cmd/opm/alpha/template/composite.go @@ -0,0 +1,183 @@ +package template + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/operator-framework/operator-registry/alpha/template/composite" + "github.com/operator-framework/operator-registry/cmd/opm/internal/util" +) + +func newCompositeTemplateCmd() *cobra.Command { + var ( + template composite.Template + output string + containerTool 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) { + containerTool = "docker" + catalogData, err := os.Open(catalogFile) + if err != nil { + log.Fatalf("opening catalog config file %q: %s", catalogFile, err) + } + defer catalogData.Close() + + // get catalog configurations + catalogConfig := &composite.CatalogConfig{} + catalogDoc := json.RawMessage{} + catalogDecoder := yaml.NewYAMLOrJSONDecoder(catalogData, 4096) + err = catalogDecoder.Decode(&catalogDoc) + if err != nil { + log.Fatalf("decoding catalog config: %s", err) + } + err = json.Unmarshal(catalogDoc, catalogConfig) + if err != nil { + log.Fatalf("unmarshalling catalog config: %s", err) + } + + if catalogConfig.Schema != composite.CatalogSchema { + log.Fatalf("catalog configuration file has unknown schema, should be %q", composite.CatalogSchema) + } + + catalogBuilderMap := make(composite.CatalogBuilderMap) + + wd, err := os.Getwd() + if err != nil { + log.Fatalf("getting current working directory: %s", err) + } + + // setup the builders for each catalog + setupFailed := false + setupErrors := map[string][]string{} + for _, catalog := range catalogConfig.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(composite.BuilderMap) + for _, schema := range catalog.Builders { + builder, err := builderForSchema(schema, composite.BuilderConfig{ + ContainerCfg: composite.ContainerConfig{ + ContainerTool: containerTool, + BaseImage: catalog.Destination.BaseImage, + WorkingDir: catalog.Destination.WorkingDir, + }, + OutputType: output, + // BUGBUG: JEK: This is a strong assumption that input is always local to execution which we need to eliminate in a later PR + InputDirectory: wd, + }) + if err != nil { + log.Fatalf("getting builder %q for catalog %q: %s", 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 %s:\n", cat) + for _, err := range errs { + errMsg += fmt.Sprintf(" - %s\n", err) + } + } + log.Fatalf("catalog configuration file field validation failed: %s", errMsg) + } + + template.CatalogBuilders = catalogBuilderMap + + reg, err := util.CreateCLIRegistry(cmd) + if err != nil { + log.Fatalf("creating containerd registry: %v", err) + } + defer reg.Destroy() + + template.Registry = reg + + compositeData, err := os.Open(compositeFile) + if err != nil { + log.Fatalf("opening composite config file %q: %s", compositeFile, err) + } + defer compositeData.Close() + + // parse data to composite config + compositeConfig := &composite.CompositeConfig{} + compositeDoc := json.RawMessage{} + compositeDecoder := yaml.NewYAMLOrJSONDecoder(compositeData, 4096) + err = compositeDecoder.Decode(&compositeDoc) + if err != nil { + log.Fatalf("decoding composite config: %s", err) + } + err = json.Unmarshal(compositeDoc, compositeConfig) + if err != nil { + log.Fatalf("unmarshalling composite config: %s", err) + } + + if compositeConfig.Schema != composite.CompositeSchema { + log.Fatalf("%q has unknown schema, should be %q", compositeFile, composite.CompositeSchema) + } + + err = template.Render(cmd.Context(), compositeConfig, validate) + if err != nil { + log.Fatalf("rendering the composite template: %s", err) + } + }, + } + cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") + // TODO: Investigate ways to do this without using a cli tool like docker/podman + // cmd.Flags().StringVar(&containerTool, "container-tool", "docker", "container tool to be used when rendering templates (should be an equivalent replacement to docker - similar to podman)") + 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", "catalog/config.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 +} + +func builderForSchema(schema string, builderCfg composite.BuilderConfig) (composite.Builder, error) { + var builder composite.Builder + switch schema { + case composite.BasicBuilderSchema: + builder = composite.NewBasicBuilder(builderCfg) + case composite.SemverBuilderSchema: + builder = composite.NewSemverBuilder(builderCfg) + case composite.RawBuilderSchema: + builder = composite.NewRawBuilder(builderCfg) + case composite.CustomBuilderSchema: + builder = composite.NewCustomBuilder(builderCfg) + default: + return nil, fmt.Errorf("unknown schema %q", schema) + } + + return builder, nil +} diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/semver.go b/staging/operator-registry/cmd/opm/alpha/template/semver.go similarity index 75% rename from vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/semver.go rename to staging/operator-registry/cmd/opm/alpha/template/semver.go index 0dd4bd122d..dd27fda074 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/semver.go +++ b/staging/operator-registry/cmd/opm/alpha/template/semver.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "fmt" @@ -10,19 +10,19 @@ import ( "github.com/sirupsen/logrus" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/veneer/semver" + "github.com/operator-framework/operator-registry/alpha/template/semver" "github.com/operator-framework/operator-registry/cmd/opm/internal/util" "github.com/spf13/cobra" ) -func newSemverCmd() *cobra.Command { +func newSemverTemplateCmd() *cobra.Command { output := "" cmd := &cobra.Command{ Use: "semver [FILE]", - Short: `Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, - Long: `Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, + 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 @@ -52,7 +52,7 @@ When FILE is '-' or not provided, the veneer is read from standard input`, // 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 veneer.Render and logged as fatal errors. + // returned from template.Render and logged as fatal errors. logrus.SetOutput(ioutil.Discard) reg, err := util.CreateCLIRegistry(cmd) @@ -61,11 +61,11 @@ When FILE is '-' or not provided, the veneer is read from standard input`, } defer reg.Destroy() - veneer := semver.Veneer{ + template := semver.Template{ Data: data, Registry: reg, } - out, err := veneer.Render(cmd.Context()) + out, err := template.Render(cmd.Context()) if err != nil { log.Fatalf("semver %q: %v", source, err) } diff --git a/staging/operator-registry/cmd/opm/root/cmd.go b/staging/operator-registry/cmd/opm/root/cmd.go index 9a551f1457..71631c6129 100644 --- a/staging/operator-registry/cmd/opm/root/cmd.go +++ b/staging/operator-registry/cmd/opm/root/cmd.go @@ -28,6 +28,7 @@ func NewCmd() *cobra.Command { return nil }, Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy } cmd.PersistentFlags().Bool("skip-tls", false, "skip TLS certificate verification for container image registries while pulling bundles or index") diff --git a/staging/operator-registry/opm-example.Dockerfile b/staging/operator-registry/opm-example.Dockerfile index a59dfb1af5..3e368fbc03 100644 --- a/staging/operator-registry/opm-example.Dockerfile +++ b/staging/operator-registry/opm-example.Dockerfile @@ -4,10 +4,11 @@ FROM quay.io/operator-framework/opm:latest # Configure the entrypoint and command ENTRYPOINT ["/bin/opm"] -CMD ["serve", "/configs"] +CMD ["serve", "/configs", "--cache-dir=/tmp/cache"] -# Copy declarative config root into image at /configs +# Copy declarative config root into image at /configs and pre-populate serve cache ADD index /configs +RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"] # Set DC-specific label for the location of the DC root directory # in the image diff --git a/staging/operator-registry/pkg/image/containerdregistry/registry.go b/staging/operator-registry/pkg/image/containerdregistry/registry.go index 6519539e5b..a5bacb4fab 100644 --- a/staging/operator-registry/pkg/image/containerdregistry/registry.go +++ b/staging/operator-registry/pkg/image/containerdregistry/registry.go @@ -47,7 +47,7 @@ func (r *Registry) Pull(ctx context.Context, ref image.Reference) error { name, root, err := r.resolver.Resolve(ctx, ref.String()) if err != nil { - return fmt.Errorf("error resolving name %s: %v", name, err) + return fmt.Errorf("error resolving name for image ref %s: %v", ref.String(), err) } r.log.Debugf("resolved name: %s", name) diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/load.go b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/load.go index 65c289780e..98b25da02a 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/load.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/load.go @@ -1,6 +1,7 @@ package declcfg import ( + "bytes" "encoding/json" "errors" "fmt" @@ -135,7 +136,7 @@ func LoadReader(r io.Reader) (*DeclarativeConfig, error) { var in Meta if err := json.Unmarshal(doc, &in); err != nil { - return nil, err + return nil, fmt.Errorf("unmarshal error: %s", resolveUnmarshalErr(doc, err)) } switch in.Schema { @@ -186,3 +187,47 @@ func LoadFile(root fs.FS, path string) (*DeclarativeConfig, error) { return cfg, nil } + +func resolveUnmarshalErr(data []byte, err error) string { + var te *json.UnmarshalTypeError + if errors.As(err, &te) { + return formatUnmarshallErrorString(data, te.Error(), te.Offset) + } + var se *json.SyntaxError + if errors.As(err, &se) { + return formatUnmarshallErrorString(data, se.Error(), se.Offset) + } + return err.Error() +} + +func formatUnmarshallErrorString(data []byte, errmsg string, offset int64) string { + sb := new(strings.Builder) + _, _ = sb.WriteString(fmt.Sprintf("%s at offset %d (indicated by <==)\n ", errmsg, offset)) + // attempt to present the erroneous JSON in indented, human-readable format + // errors result in presenting the original, unformatted output + var pretty bytes.Buffer + err := json.Indent(&pretty, data, "", " ") + if err == nil { + pString := pretty.String() + // calc the prettified string offset which correlates to the original string offset + var pOffset, origOffset int64 + origOffset = 0 + for origOffset = 0; origOffset < offset; { + pOffset++ + if pString[pOffset] != '\n' && pString[pOffset] != ' ' { + origOffset++ + } + } + _, _ = sb.WriteString(pString[:pOffset]) + _, _ = sb.WriteString(" <== ") + _, _ = sb.WriteString(pString[pOffset:]) + } else { + for i := int64(0); i < offset; i++ { + _ = sb.WriteByte(data[i]) + } + _, _ = sb.WriteString(" <== ") + _, _ = sb.Write(data[offset:]) + } + + return sb.String() +} diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/basic/basic.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/basic/basic.go similarity index 70% rename from vendor/github.com/operator-framework/operator-registry/alpha/veneer/basic/basic.go rename to vendor/github.com/operator-framework/operator-registry/alpha/template/basic/basic.go index 26c2788b57..18566fbcf2 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/basic/basic.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/basic/basic.go @@ -10,11 +10,11 @@ import ( "github.com/operator-framework/operator-registry/pkg/image" ) -type Veneer struct { +type Template struct { Registry image.Registry } -func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { +func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) { cfg, err := declcfg.LoadReader(reader) if err != nil { return cfg, err @@ -23,13 +23,13 @@ func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.Declarat 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: v.Registry, + Registry: t.Registry, AllowedRefMask: action.RefBundleImage, } for _, b := range cfg.Bundles { - if !isBundleVeneer(&b) { - return nil, fmt.Errorf("unexpected fields present in basic veneer bundle") + if !isBundleTemplate(&b) { + return nil, fmt.Errorf("unexpected fields present in basic template bundle") } r.Refs = []string{b.Image} contributor, err := r.Run(ctx) @@ -43,8 +43,8 @@ func (v Veneer) Render(ctx context.Context, reader io.Reader) (*declcfg.Declarat return cfg, nil } -// isBundleVeneer identifies a Bundle veneer source as having a Schema and Image defined +// isBundleTemplate identifies a Bundle template source as having a Schema and Image defined // but no Properties, RelatedImages or Package defined -func isBundleVeneer(b *declcfg.Bundle) bool { +func isBundleTemplate(b *declcfg.Bundle) bool { return b.Schema != "" && b.Image != "" && b.Package == "" && len(b.Properties) == 0 && len(b.RelatedImages) == 0 } diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/builder.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/builder.go new file mode 100644 index 0000000000..3d1f7af95d --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/builder.go @@ -0,0 +1,349 @@ +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 ContainerConfig struct { + ContainerTool string + BaseImage string + WorkingDir string +} + +type BuilderConfig struct { + ContainerCfg ContainerConfig + OutputType string + InputDirectory string +} + +type Builder interface { + Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error + Validate(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 { + 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.ContainerCfg.WorkingDir, dir, basicConfig.Output) + + return build(dcfg, destPath, bb.builderCfg.OutputType) +} + +func (bb *BasicBuilder) Validate(dir string) error { + return validate(bb.builderCfg.ContainerCfg, 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 { + 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.ContainerCfg.WorkingDir, dir, semverConfig.Output) + + return build(dcfg, destPath, sb.builderCfg.OutputType) +} + +func (sb *SemverBuilder) Validate(dir string) error { + return validate(sb.builderCfg.ContainerCfg, 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 { + return fmt.Errorf("error reading raw input file: %s, %v", rawConfig.Input, 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.ContainerCfg.WorkingDir, dir, rawConfig.Output) + + return build(dcfg, destPath, rb.builderCfg.OutputType) +} + +func (rb *RawBuilder) Validate(dir string) error { + return validate(rb.builderCfg.ContainerCfg, 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...) + cmd.Dir = cb.builderCfg.ContainerCfg.WorkingDir + + // 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", cmd.String(), err) + } + + 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.ContainerCfg.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(dir string) error { + return validate(cb.builderCfg.ContainerCfg, 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(containerCfg ContainerConfig, dir string) error { + + path := path.Join(containerCfg.WorkingDir, dir) + s, err := os.Stat(path) + if err != nil { + return fmt.Errorf("directory not found. validation path needs to be composed of ContainerConfig.WorkingDir+Component[].Destination.Path: %q: %v", path, err) + } + if !s.IsDir() { + return fmt.Errorf("%q is not a directory", path) + } + + if err := config.Validate(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/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/composite.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/composite.go new file mode 100644 index 0000000000..422017c974 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/composite.go @@ -0,0 +1,50 @@ +package composite + +import ( + "context" + "fmt" + + "github.com/operator-framework/operator-registry/pkg/image" +) + +type BuilderMap map[string]Builder + +type CatalogBuilderMap map[string]BuilderMap + +type Template struct { + CatalogBuilders CatalogBuilderMap + Registry image.Registry +} + +// TODO(everettraven): do we need the context here? If so, how should it be used? +func (t *Template) Render(ctx context.Context, config *CompositeConfig, validate bool) error { + // TODO(everettraven): should we return aggregated errors? + for _, component := range config.Components { + if builderMap, ok := t.CatalogBuilders[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(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 t.CatalogBuilders { + 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 +} diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/config.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/config.go new file mode 100644 index 0000000000..70a480d4e8 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/config.go @@ -0,0 +1,42 @@ +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/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/types.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/types.go new file mode 100644 index 0000000000..5295a5ddfd --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/composite/types.go @@ -0,0 +1,29 @@ +package composite + +import "encoding/json" + +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 +} diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/README.md b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/README.md similarity index 70% rename from vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/README.md rename to vendor/github.com/operator-framework/operator-registry/alpha/template/semver/README.md index e69726c487..2bab90c1c0 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/README.md +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/README.md @@ -1,23 +1,23 @@ -## Semver Veneer: +## Semver Template: -Since a `veneer` is identified as an input schema which may be processed to generate a valid FBC, we can define a `semver veneer` as a schema which uses channel conventions to facilitate the auto-generation of channels along `semver` delimiters. +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, it cannot be used in any bundle included in the `semver veneer` and will result in a fatal error.] +[**DISCLAIMER:** since version build metadata [MUST be ignored when determining version precedence](https://semver.org) when using semver, it cannot be used in any bundle included in the `semver template` and will result in a fatal error.] ### Schema Goals -The `semver veneer` must have: +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 veneer entities +The resulting FBC must clearly indicate how generated channels relate to template entities ### Schema Anatomy -For convenience and simplicity, this veneer 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. +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 veneer will generate X-stream or Y-stream channels (attributes can be set independently). If omitted, only minor (Y-stream) channels will be generated. +`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. @@ -54,12 +54,12 @@ In this example, `Candidate` has the entire version range of bundles, `Fast` ha ### CLI Tool Usage ``` -% ./bin/opm alpha render-veneer semver -h -Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input +% ./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-veneer semver [FILE] [flags] + opm alpha render-template semver [FILE] [flags] Flags: -h, --help help for semver @@ -73,19 +73,19 @@ Global Flags: Example command usage: ``` # Example with file argument passed in -opm alpha render-veneer semver infile.semver.veneer.yaml +opm alpha render-template semver infile.semver.template.yaml # Example with no file argument passed in -opm alpha render-veneer semver -o yaml < infile.semver.veneer.yaml > outfile.yaml +opm alpha render-template semver -o yaml < infile.semver.template.yaml > outfile.yaml # Example with "-" as the file argument passed in -cat infile.semver.veneer.yaml | opm alpha render-veneer semver -o mermaid - +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 veneer attribute `GenerateMajorChannels: true` resulting major channels from the command are (skipping the rendered bundle image output): +With the template attribute `GenerateMajorChannels: true` resulting major channels from the command are (skipping the rendered bundle image output): ```yaml --- defaultChannel: stable-v1 @@ -151,10 +151,10 @@ package: testoperator schema: olm.channel ``` -We generated a channel for each veneer 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. +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 veneer attribute `GenerateMinorChannels: true` and running the command again (again skipping rendered bundle image output) we get a bunch more channels: +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 @@ -245,7 +245,7 @@ package: testoperator schema: olm.channel ``` -Here, a channel is generated for each veneer channel which differs by minor version, and each channel has a `replaces` edge from the predecessor channel to the next-lesser minor bundle version. 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. +Here, a channel is generated for each template channel which differs by minor version, and each channel has a `replaces` edge from the predecessor channel to the next-lesser minor bundle version. 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 diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/major-version-demo.gif b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/major-version-demo.gif new file mode 100644 index 0000000000..244ecd6dbb Binary files /dev/null and b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/major-version-demo.gif differ diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/minor-version-demo.gif b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/minor-version-demo.gif new file mode 100644 index 0000000000..af0fea0b36 Binary files /dev/null and b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/minor-version-demo.gif differ diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/semver.go b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/semver.go similarity index 89% rename from vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/semver.go rename to vendor/github.com/operator-framework/operator-registry/alpha/template/semver/semver.go index 00edcc64f4..0bb623cd23 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/semver.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/template/semver/semver.go @@ -17,27 +17,27 @@ import ( ) // data passed into this module externally -type Veneer struct { +type Template struct { Data io.Reader Registry image.Registry } // IO structs -- BEGIN -type semverVeneerBundleEntry struct { +type semverTemplateBundleEntry struct { Image string `json:"image,omitempty"` } type candidateBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } type fastBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } type stableBundles struct { - Bundles []semverVeneerBundleEntry `json:"bundles,omitempty"` + Bundles []semverTemplateBundleEntry `json:"bundles,omitempty"` } -type semverVeneer struct { +type semverTemplate struct { Schema string `json:"schema"` GenerateMajorChannels bool `json:"generateMajorChannels,omitempty"` GenerateMinorChannels bool `json:"generateMinorChannels,omitempty"` @@ -75,10 +75,10 @@ func (b byChannelPriority) Swap(i, j int) { b[i], b[j] = b[j], b[i] } // channels --> bundles --> version type semverRenderedChannelVersions map[string]map[string]semver.Version // e.g. d["stable-v1"]["example-operator/v1.0.0"] = 1.0.0 -func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) { +func (t Template) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) { var out declcfg.DeclarativeConfig - sv, err := readFile(v.Data) + sv, err := readFile(t.Data) if err != nil { return nil, fmt.Errorf("semver-render: unable to read file: %v", err) } @@ -94,7 +94,7 @@ func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) r := action.Render{ AllowedRefMask: action.RefBundleImage, Refs: []string{b}, - Registry: v.Registry, + Registry: t.Registry, } c, err := r.Run(ctx) if err != nil { @@ -120,7 +120,7 @@ func (v Veneer) Render(ctx context.Context) (*declcfg.DeclarativeConfig, error) return &out, nil } -func buildBundleList(bundles *[]semverVeneerBundleEntry, dict *map[string]struct{}) { +func buildBundleList(bundles *[]semverTemplateBundleEntry, dict *map[string]struct{}) { for _, b := range *bundles { if _, ok := (*dict)[b.Image]; !ok { (*dict)[b.Image] = struct{}{} @@ -128,25 +128,25 @@ func buildBundleList(bundles *[]semverVeneerBundleEntry, dict *map[string]struct } } -func readFile(data io.Reader) (*semverVeneer, error) { - fileData, err := io.ReadAll(data) +func readFile(reader io.Reader) (*semverTemplate, error) { + fileData, err := io.ReadAll(reader) if err != nil { return nil, err } // default behavior is to generate only minor channels and to use skips over replaces - sv := semverVeneer{ + sv := semverTemplate{ GenerateMajorChannels: false, GenerateMinorChannels: true, AvoidSkipPatch: false, } - if err := yaml.Unmarshal(fileData, &sv); err != nil { + if err := yaml.UnmarshalStrict(fileData, &sv); err != nil { return nil, err } return &sv, nil } -func (sv *semverVeneer) getVersionsFromStandardChannels(cfg *declcfg.DeclarativeConfig) (*semverRenderedChannelVersions, error) { +func (sv *semverTemplate) getVersionsFromStandardChannels(cfg *declcfg.DeclarativeConfig) (*semverRenderedChannelVersions, error) { versions := semverRenderedChannelVersions{} bdm, err := sv.getVersionsFromChannel(sv.Candidate.Bundles, cfg) @@ -179,15 +179,15 @@ func (sv *semverVeneer) getVersionsFromStandardChannels(cfg *declcfg.Declarative return &versions, nil } -func (sv *semverVeneer) getVersionsFromChannel(semverBundles []semverVeneerBundleEntry, cfg *declcfg.DeclarativeConfig) (map[string]semver.Version, error) { +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 veneer, to: + // 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 veneer bundles to rendered bundles in `entries` which is accumulated by the caller + // - 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 veneer is present in the successfully-rendered bundles + // 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 { @@ -247,11 +247,11 @@ func (h *highwaterChannel) gt(ih *highwaterChannel) bool { return (channelPriorities[h.kind] > channelPriorities[ih.kind]) || (h.version.GT(ih.version)) } -// generates an unlinked channel for each channel as per the input veneer config (major || minor), then link up the edges of the set of channels so that: +// 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 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 minor versions of the bundle enumerated in the veneer. +// - within the same minor version (Y-stream), the head of the channel should have a 'skips' encompassing all lesser minor 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 *semverVeneer) generateChannels(semverChannels *semverRenderedChannelVersions) []declcfg.Channel { +func (sv *semverTemplate) generateChannels(semverChannels *semverRenderedChannelVersions) []declcfg.Channel { outChannels := []declcfg.Channel{} // sort the channelkinds in ascending order so we can traverse the bundles in order of @@ -334,7 +334,7 @@ func (sv *semverVeneer) generateChannels(semverChannels *semverRenderedChannelVe } // all channels that come to linkChannels MUST have the same prefix. This adds replaces edges of minor versions of the largest major version. -func (sv *semverVeneer) linkChannels(unlinkedChannels map[string]*declcfg.Channel, pkg string, semverChannels *semverRenderedChannelVersions, channelMapping *map[string]string) []declcfg.Channel { +func (sv *semverTemplate) linkChannels(unlinkedChannels map[string]*declcfg.Channel, pkg string, semverChannels *semverRenderedChannelVersions, channelMapping *map[string]string) []declcfg.Channel { channels := []declcfg.Channel{} for channelName, channel := range unlinkedChannels { diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/major-version-demo.gif b/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/major-version-demo.gif deleted file mode 100644 index bf2faa3795..0000000000 Binary files a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/major-version-demo.gif and /dev/null differ diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/minor-version-demo.gif b/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/minor-version-demo.gif deleted file mode 100644 index 3644292b92..0000000000 Binary files a/vendor/github.com/operator-framework/operator-registry/alpha/veneer/semver/minor-version-demo.gif and /dev/null differ diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go index 74e88edbe7..55511cb2ea 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go @@ -6,7 +6,7 @@ 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/veneer" + "github.com/operator-framework/operator-registry/cmd/opm/alpha/template" ) func NewCmd() *cobra.Command { @@ -15,13 +15,14 @@ func NewCmd() *cobra.Command { Use: "alpha", Short: "Run an alpha subcommand", Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy } runCmd.AddCommand( bundle.NewCmd(), list.NewCmd(), rendergraph.NewCmd(), - veneer.NewCmd(), + template.NewCmd(), ) return runCmd } diff --git a/staging/operator-registry/cmd/opm/alpha/veneer/basic.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/basic.go similarity index 70% rename from staging/operator-registry/cmd/opm/alpha/veneer/basic.go rename to vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/basic.go index 40311c0eb2..2510a4fa74 100644 --- a/staging/operator-registry/cmd/opm/alpha/veneer/basic.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/basic.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "io" @@ -10,21 +10,21 @@ import ( "github.com/spf13/cobra" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/veneer/basic" + "github.com/operator-framework/operator-registry/alpha/template/basic" "github.com/operator-framework/operator-registry/cmd/opm/internal/util" ) -func newBasicVeneerRenderCmd() *cobra.Command { +func newBasicTemplateCmd() *cobra.Command { var ( - veneer basic.Veneer - output string + template basic.Template + output string ) cmd := &cobra.Command{ - Use: "basic basic-veneer-file", - Short: `Generate a file-based catalog from a single 'basic veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, - Long: `Generate a file-based catalog from a single 'basic veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, + 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 @@ -49,7 +49,7 @@ When FILE is '-' or not provided, the veneer is read from standard input`, // 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 veneer.Render and logged as fatal errors. + // returned from template.Render and logged as fatal errors. logrus.SetOutput(ioutil.Discard) reg, err := util.CreateCLIRegistry(cmd) @@ -58,10 +58,10 @@ When FILE is '-' or not provided, the veneer is read from standard input`, } defer reg.Destroy() - veneer.Registry = reg + template.Registry = reg // only taking first file argument - cfg, err := veneer.Render(cmd.Context(), data) + cfg, err := template.Render(cmd.Context(), data) if err != nil { log.Fatal(err) } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/cmd.go similarity index 64% rename from vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/cmd.go rename to vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/cmd.go index 2bdec4e1f7..1c435e6fa2 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/cmd.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "io" @@ -9,13 +9,14 @@ import ( func NewCmd() *cobra.Command { runCmd := &cobra.Command{ - Use: "render-veneer", - Short: "Render a veneer type", + Use: "render-template", + Short: "Render a catalog template type", Args: cobra.NoArgs, } - runCmd.AddCommand(newBasicVeneerRenderCmd()) - runCmd.AddCommand(newSemverCmd()) + runCmd.AddCommand(newBasicTemplateCmd()) + runCmd.AddCommand(newSemverTemplateCmd()) + runCmd.AddCommand(newCompositeTemplateCmd()) return runCmd } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/composite.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/composite.go new file mode 100644 index 0000000000..1d8b9df0cb --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/composite.go @@ -0,0 +1,183 @@ +package template + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/operator-framework/operator-registry/alpha/template/composite" + "github.com/operator-framework/operator-registry/cmd/opm/internal/util" +) + +func newCompositeTemplateCmd() *cobra.Command { + var ( + template composite.Template + output string + containerTool 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) { + containerTool = "docker" + catalogData, err := os.Open(catalogFile) + if err != nil { + log.Fatalf("opening catalog config file %q: %s", catalogFile, err) + } + defer catalogData.Close() + + // get catalog configurations + catalogConfig := &composite.CatalogConfig{} + catalogDoc := json.RawMessage{} + catalogDecoder := yaml.NewYAMLOrJSONDecoder(catalogData, 4096) + err = catalogDecoder.Decode(&catalogDoc) + if err != nil { + log.Fatalf("decoding catalog config: %s", err) + } + err = json.Unmarshal(catalogDoc, catalogConfig) + if err != nil { + log.Fatalf("unmarshalling catalog config: %s", err) + } + + if catalogConfig.Schema != composite.CatalogSchema { + log.Fatalf("catalog configuration file has unknown schema, should be %q", composite.CatalogSchema) + } + + catalogBuilderMap := make(composite.CatalogBuilderMap) + + wd, err := os.Getwd() + if err != nil { + log.Fatalf("getting current working directory: %s", err) + } + + // setup the builders for each catalog + setupFailed := false + setupErrors := map[string][]string{} + for _, catalog := range catalogConfig.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(composite.BuilderMap) + for _, schema := range catalog.Builders { + builder, err := builderForSchema(schema, composite.BuilderConfig{ + ContainerCfg: composite.ContainerConfig{ + ContainerTool: containerTool, + BaseImage: catalog.Destination.BaseImage, + WorkingDir: catalog.Destination.WorkingDir, + }, + OutputType: output, + // BUGBUG: JEK: This is a strong assumption that input is always local to execution which we need to eliminate in a later PR + InputDirectory: wd, + }) + if err != nil { + log.Fatalf("getting builder %q for catalog %q: %s", 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 %s:\n", cat) + for _, err := range errs { + errMsg += fmt.Sprintf(" - %s\n", err) + } + } + log.Fatalf("catalog configuration file field validation failed: %s", errMsg) + } + + template.CatalogBuilders = catalogBuilderMap + + reg, err := util.CreateCLIRegistry(cmd) + if err != nil { + log.Fatalf("creating containerd registry: %v", err) + } + defer reg.Destroy() + + template.Registry = reg + + compositeData, err := os.Open(compositeFile) + if err != nil { + log.Fatalf("opening composite config file %q: %s", compositeFile, err) + } + defer compositeData.Close() + + // parse data to composite config + compositeConfig := &composite.CompositeConfig{} + compositeDoc := json.RawMessage{} + compositeDecoder := yaml.NewYAMLOrJSONDecoder(compositeData, 4096) + err = compositeDecoder.Decode(&compositeDoc) + if err != nil { + log.Fatalf("decoding composite config: %s", err) + } + err = json.Unmarshal(compositeDoc, compositeConfig) + if err != nil { + log.Fatalf("unmarshalling composite config: %s", err) + } + + if compositeConfig.Schema != composite.CompositeSchema { + log.Fatalf("%q has unknown schema, should be %q", compositeFile, composite.CompositeSchema) + } + + err = template.Render(cmd.Context(), compositeConfig, validate) + if err != nil { + log.Fatalf("rendering the composite template: %s", err) + } + }, + } + cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") + // TODO: Investigate ways to do this without using a cli tool like docker/podman + // cmd.Flags().StringVar(&containerTool, "container-tool", "docker", "container tool to be used when rendering templates (should be an equivalent replacement to docker - similar to podman)") + 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", "catalog/config.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 +} + +func builderForSchema(schema string, builderCfg composite.BuilderConfig) (composite.Builder, error) { + var builder composite.Builder + switch schema { + case composite.BasicBuilderSchema: + builder = composite.NewBasicBuilder(builderCfg) + case composite.SemverBuilderSchema: + builder = composite.NewSemverBuilder(builderCfg) + case composite.RawBuilderSchema: + builder = composite.NewRawBuilder(builderCfg) + case composite.CustomBuilderSchema: + builder = composite.NewCustomBuilder(builderCfg) + default: + return nil, fmt.Errorf("unknown schema %q", schema) + } + + return builder, nil +} diff --git a/staging/operator-registry/cmd/opm/alpha/veneer/semver.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/semver.go similarity index 75% rename from staging/operator-registry/cmd/opm/alpha/veneer/semver.go rename to vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/semver.go index 0dd4bd122d..dd27fda074 100644 --- a/staging/operator-registry/cmd/opm/alpha/veneer/semver.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/template/semver.go @@ -1,4 +1,4 @@ -package veneer +package template import ( "fmt" @@ -10,19 +10,19 @@ import ( "github.com/sirupsen/logrus" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/veneer/semver" + "github.com/operator-framework/operator-registry/alpha/template/semver" "github.com/operator-framework/operator-registry/cmd/opm/internal/util" "github.com/spf13/cobra" ) -func newSemverCmd() *cobra.Command { +func newSemverTemplateCmd() *cobra.Command { output := "" cmd := &cobra.Command{ Use: "semver [FILE]", - Short: `Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, - Long: `Generate a file-based catalog from a single 'semver veneer' file -When FILE is '-' or not provided, the veneer is read from standard input`, + 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 @@ -52,7 +52,7 @@ When FILE is '-' or not provided, the veneer is read from standard input`, // 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 veneer.Render and logged as fatal errors. + // returned from template.Render and logged as fatal errors. logrus.SetOutput(ioutil.Discard) reg, err := util.CreateCLIRegistry(cmd) @@ -61,11 +61,11 @@ When FILE is '-' or not provided, the veneer is read from standard input`, } defer reg.Destroy() - veneer := semver.Veneer{ + template := semver.Template{ Data: data, Registry: reg, } - out, err := veneer.Render(cmd.Context()) + out, err := template.Render(cmd.Context()) if err != nil { log.Fatalf("semver %q: %v", source, err) } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go index 9a551f1457..71631c6129 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go @@ -28,6 +28,7 @@ func NewCmd() *cobra.Command { return nil }, Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy } cmd.PersistentFlags().Bool("skip-tls", false, "skip TLS certificate verification for container image registries while pulling bundles or index") diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/image/containerdregistry/registry.go b/vendor/github.com/operator-framework/operator-registry/pkg/image/containerdregistry/registry.go index 6519539e5b..a5bacb4fab 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/image/containerdregistry/registry.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/image/containerdregistry/registry.go @@ -47,7 +47,7 @@ func (r *Registry) Pull(ctx context.Context, ref image.Reference) error { name, root, err := r.resolver.Resolve(ctx, ref.String()) if err != nil { - return fmt.Errorf("error resolving name %s: %v", name, err) + return fmt.Errorf("error resolving name for image ref %s: %v", ref.String(), err) } r.log.Debugf("resolved name: %s", name) diff --git a/vendor/modules.txt b/vendor/modules.txt index 7adf0decf7..1d9773422d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -753,8 +753,9 @@ github.com/operator-framework/operator-registry/alpha/action github.com/operator-framework/operator-registry/alpha/declcfg github.com/operator-framework/operator-registry/alpha/model github.com/operator-framework/operator-registry/alpha/property -github.com/operator-framework/operator-registry/alpha/veneer/basic -github.com/operator-framework/operator-registry/alpha/veneer/semver +github.com/operator-framework/operator-registry/alpha/template/basic +github.com/operator-framework/operator-registry/alpha/template/composite +github.com/operator-framework/operator-registry/alpha/template/semver github.com/operator-framework/operator-registry/cmd/configmap-server github.com/operator-framework/operator-registry/cmd/initializer github.com/operator-framework/operator-registry/cmd/opm @@ -762,7 +763,7 @@ github.com/operator-framework/operator-registry/cmd/opm/alpha github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle github.com/operator-framework/operator-registry/cmd/opm/alpha/list github.com/operator-framework/operator-registry/cmd/opm/alpha/render-graph -github.com/operator-framework/operator-registry/cmd/opm/alpha/veneer +github.com/operator-framework/operator-registry/cmd/opm/alpha/template github.com/operator-framework/operator-registry/cmd/opm/generate github.com/operator-framework/operator-registry/cmd/opm/index github.com/operator-framework/operator-registry/cmd/opm/init