diff --git a/alpha/action/migrate.go b/alpha/action/migrate.go index 8cca8b897..c5880f000 100644 --- a/alpha/action/migrate.go +++ b/alpha/action/migrate.go @@ -1,13 +1,10 @@ package action import ( - "bytes" "context" "fmt" - "io" "io/ioutil" "os" - "path/filepath" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/pkg/image" @@ -17,13 +14,11 @@ type Migrate struct { CatalogRef string OutputDir string - WriteFunc WriteFunc + WriteFunc declcfg.WriteFunc FileExt string Registry image.Registry } -type WriteFunc func(config declcfg.DeclarativeConfig, w io.Writer) error - func (m Migrate) Run(ctx context.Context) error { entries, err := ioutil.ReadDir(m.OutputDir) if err != nil && !os.IsNotExist(err) { @@ -52,48 +47,5 @@ func (m Migrate) Run(ctx context.Context) error { return fmt.Errorf("render catalog image: %w", err) } - return writeToFS(*cfg, m.OutputDir, m.WriteFunc, m.FileExt) -} - -func writeToFS(cfg declcfg.DeclarativeConfig, rootDir string, writeFunc WriteFunc, fileExt string) error { - channelsByPackage := map[string][]declcfg.Channel{} - for _, c := range cfg.Channels { - channelsByPackage[c.Package] = append(channelsByPackage[c.Package], c) - } - bundlesByPackage := map[string][]declcfg.Bundle{} - for _, b := range cfg.Bundles { - bundlesByPackage[b.Package] = append(bundlesByPackage[b.Package], b) - } - - if err := os.MkdirAll(rootDir, 0777); err != nil { - return err - } - - for _, p := range cfg.Packages { - fcfg := declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{p}, - Channels: channelsByPackage[p.Name], - Bundles: bundlesByPackage[p.Name], - } - pkgDir := filepath.Join(rootDir, p.Name) - if err := os.MkdirAll(pkgDir, 0777); err != nil { - return err - } - filename := filepath.Join(pkgDir, fmt.Sprintf("catalog%s", fileExt)) - if err := writeFile(fcfg, filename, writeFunc); err != nil { - return err - } - } - return nil -} - -func writeFile(cfg declcfg.DeclarativeConfig, filename string, writeFunc WriteFunc) error { - buf := &bytes.Buffer{} - if err := writeFunc(cfg, buf); err != nil { - return fmt.Errorf("write to buffer for %q: %v", filename, err) - } - if err := ioutil.WriteFile(filename, buf.Bytes(), 0666); err != nil { - return fmt.Errorf("write file %q: %v", filename, err) - } - return nil + return declcfg.WriteFS(*cfg, m.OutputDir, m.WriteFunc, m.FileExt) } diff --git a/alpha/action/migrate_test.go b/alpha/action/migrate_test.go index ecaa0b367..5f6be7b95 100644 --- a/alpha/action/migrate_test.go +++ b/alpha/action/migrate_test.go @@ -2,7 +2,6 @@ package action_test import ( "context" - "fmt" "io/fs" "os" "path/filepath" @@ -111,7 +110,6 @@ func TestMigrate(t *testing.T) { path := filepath.Join(s.migrate.OutputDir, file) actualData, err := os.ReadFile(path) require.NoError(t, err) - fmt.Println(string(actualData)) require.Equal(t, expectedData, string(actualData)) } }) @@ -212,12 +210,18 @@ properties: value: packageName: bar versionRange: <0.1.0 -- type: olm.bundle.object +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMS4wIn0sIm5hbWUiOiJmb28udjAuMS4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 + annotations: + olm.skipRange: <0.1.0 + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: Foo + name: foos.test.foo + version: v1 + displayName: Foo Operator + provider: {} relatedImages: - image: test.registry/foo-operator/foo-bundle:v0.1.0 name: "" @@ -247,12 +251,18 @@ properties: value: packageName: bar versionRange: <0.1.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJmb28udjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwiaW5zdGFsbCI6eyJzcGVjIjp7ImRlcGxveW1lbnRzIjpbeyJuYW1lIjoiZm9vLW9wZXJhdG9yIiwic3BlYyI6eyJ0ZW1wbGF0ZSI6eyJzcGVjIjp7ImNvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQ6djAuMi4wIn1dfX19fSx7Im5hbWUiOiJmb28tb3BlcmF0b3ItMiIsInNwZWMiOnsidGVtcGxhdGUiOnsic3BlYyI6eyJjb250YWluZXJzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvby0yOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQtMjp2MC4yLjAifV19fX19XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJyZWxhdGVkSW1hZ2VzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvbzp2MC4yLjAiLCJuYW1lIjoib3BlcmF0b3IifSx7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLW90aGVyOnYwLjIuMCIsIm5hbWUiOiJvdGhlciJ9XSwicmVwbGFjZXMiOiJmb28udjAuMS4wIiwic2tpcHMiOlsiZm9vLnYwLjEuMSIsImZvby52MC4xLjIiXSwidmVyc2lvbiI6IjAuMi4wIn19 -- type: olm.bundle.object +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 + annotations: + olm.skipRange: <0.2.0 + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: Foo + name: foos.test.foo + version: v1 + displayName: Foo Operator + provider: {} relatedImages: - image: test.registry/foo-operator/foo-2:v0.2.0 name: "" @@ -299,12 +309,15 @@ properties: value: packageName: bar version: 0.1.0 -- type: olm.bundle.object +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: Bar + name: bars.test.bar + version: v1alpha1 + provider: {} relatedImages: - image: test.registry/bar-operator/bar-bundle:v0.1.0 name: "" @@ -325,12 +338,17 @@ properties: value: packageName: bar version: 0.2.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= -- type: olm.bundle.object +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 + annotations: + olm.skipRange: <0.2.0 + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: Bar + name: bars.test.bar + version: v1alpha1 + provider: {} relatedImages: - image: test.registry/bar-operator/bar-bundle:v0.2.0 name: "" diff --git a/alpha/action/render.go b/alpha/action/render.go index 3f38390a0..6aa6fb07d 100644 --- a/alpha/action/render.go +++ b/alpha/action/render.go @@ -82,7 +82,7 @@ func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { if err != nil { return nil, fmt.Errorf("render reference %q: %w", ref, err) } - renderBundleObjects(cfg) + moveBundleObjectsToEndOfPropertySlices(cfg) for _, b := range cfg.Bundles { sort.Slice(b.RelatedImages, func(i, j int) bool { @@ -304,6 +304,7 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error if err != nil { return nil, fmt.Errorf("get related images for bundle %q: %v", bundle.Name, err) } + var csvJson []byte for _, obj := range bundle.Objects { if obj.GetKind() == "ClusterServiceVersion" { @@ -376,19 +377,21 @@ func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) { return relatedImages, nil } -func renderBundleObjects(cfg *declcfg.DeclarativeConfig) { +func moveBundleObjectsToEndOfPropertySlices(cfg *declcfg.DeclarativeConfig) { for bi, b := range cfg.Bundles { - props := b.Properties[:0] + var ( + others []property.Property + objs []property.Property + ) for _, p := range b.Properties { - if p.Type != property.TypeBundleObject { - props = append(props, p) + switch p.Type { + case property.TypeBundleObject, property.TypeCSVMetadata: + objs = append(objs, p) + default: + others = append(others, p) } } - - for _, obj := range b.Objects { - props = append(props, property.MustBuildBundleObjectData([]byte(obj))) - } - cfg.Bundles[bi].Properties = props + cfg.Bundles[bi].Properties = append(others, objs...) } } diff --git a/alpha/action/render_test.go b/alpha/action/render_test.go index c3385dabd..976da6922 100644 --- a/alpha/action/render_test.go +++ b/alpha/action/render_test.go @@ -1,16 +1,19 @@ package action_test import ( + "bytes" "context" "embed" "encoding/json" "errors" + "io" "io/fs" "os" "path/filepath" "testing" "testing/fstest" + "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/util/yaml" @@ -105,8 +108,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.1.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov1csv), - property.MustBuildBundleObjectData(foov1crd), + mustBuildCSVMetadata(bytes.NewReader(foov1csv)), }, RelatedImages: []declcfg.RelatedImage{ { @@ -130,8 +132,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.2.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov2csv), - property.MustBuildBundleObjectData(foov2crd), + mustBuildCSVMetadata(bytes.NewReader(foov2csv)), }, RelatedImages: []declcfg.RelatedImage{ { @@ -197,8 +198,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.1.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov1csv), - property.MustBuildBundleObjectData(foov1crd), + mustBuildCSVMetadata(bytes.NewReader(foov1csv)), }, RelatedImages: []declcfg.RelatedImage{ { @@ -222,8 +222,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.2.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov2csv), - property.MustBuildBundleObjectData(foov2crd), + mustBuildCSVMetadata(bytes.NewReader(foov2csv)), }, RelatedImages: []declcfg.RelatedImage{ { @@ -468,8 +467,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.2.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov2csv), - property.MustBuildBundleObjectData(foov2crd), + mustBuildCSVMetadata(bytes.NewReader(foov2csv)), }, Objects: []string{string(foov2csv), string(foov2crd)}, CsvJSON: string(foov2csv), @@ -518,8 +516,7 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.2.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObjectData(foov2csvNoRelatedImages), - property.MustBuildBundleObjectData(foov2crdNoRelatedImages), + mustBuildCSVMetadata(bytes.NewReader(foov2csvNoRelatedImages)), }, Objects: []string{string(foov2csvNoRelatedImages), string(foov2crdNoRelatedImages)}, CsvJSON: string(foov2csvNoRelatedImages), @@ -551,7 +548,17 @@ func TestRender(t *testing.T) { t.Run(s.name, func(t *testing.T) { actualCfg, actualErr := s.render.Run(context.Background()) s.assertion(t, actualErr) - require.Equal(t, s.expectCfg, actualCfg) + require.Equal(t, len(s.expectCfg.Packages), len(actualCfg.Packages)) + require.Equal(t, s.expectCfg.Packages, actualCfg.Packages) + require.Equal(t, len(s.expectCfg.Channels), len(actualCfg.Channels)) + require.Equal(t, s.expectCfg.Channels, actualCfg.Channels) + require.Equal(t, len(s.expectCfg.Bundles), len(actualCfg.Bundles)) + for i := range s.expectCfg.Bundles { + actual, expected := actualCfg.Bundles[i], s.expectCfg.Bundles[i] + require.Equal(t, expected, actual, "bundle %d", i) + } + require.Equal(t, len(s.expectCfg.Others), len(actualCfg.Others)) + require.Equal(t, s.expectCfg.Others, actualCfg.Others) }) } } @@ -880,3 +887,11 @@ func generateSqliteFile(path string, imageMap map[image.Reference]string) error } return nil } + +func mustBuildCSVMetadata(r io.Reader) property.Property { + var csv v1alpha1.ClusterServiceVersion + if err := json.NewDecoder(r).Decode(&csv); err != nil { + panic(err) + } + return property.MustBuildCSVMetadata(csv) +} diff --git a/alpha/declcfg/declcfg.go b/alpha/declcfg/declcfg.go index 80579c3a2..8c7dfb015 100644 --- a/alpha/declcfg/declcfg.go +++ b/alpha/declcfg/declcfg.go @@ -7,7 +7,9 @@ import ( "fmt" "strings" - "go4.org/bytereplacer" + "golang.org/x/text/cases" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "github.com/operator-framework/operator-registry/alpha/property" ) @@ -100,25 +102,78 @@ func (m Meta) MarshalJSON() ([]byte, error) { } func (m *Meta) UnmarshalJSON(blob []byte) error { - blob = bytereplacer.New(`\u003c`, "<", `\u003e`, ">", `\u0026`, "&").Replace(blob) + blobMap := map[string]interface{}{} + if err := json.Unmarshal(blob, &blobMap); err != nil { + return err + } + + // TODO: this function ensures we do not break backwards compatibility with + // the documented examples of FBC templates, which use upper camel case + // for JSON field names. We need to decide if we want to continue supporting + // case insensitive JSON field names, or if we want to enforce a specific + // case-sensitive key value for each field. + if err := extractUniqueMetaKeys(blobMap, m); err != nil { + return err + } + + buf := bytes.Buffer{} + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(blobMap); err != nil { + return err + } + m.Blob = buf.Bytes() + return nil +} - type tmp struct { - Schema string `json:"schema"` - Package string `json:"package,omitempty"` - Name string `json:"name,omitempty"` - Properties []property.Property `json:"properties,omitempty"` +// extractUniqueMetaKeys enables a case-insensitive key lookup for the schema, package, and name +// fields of the Meta struct. If the blobMap contains duplicate keys (that is, keys have the same folded value), +// an error is returned. +func extractUniqueMetaKeys(blobMap map[string]any, m *Meta) error { + keySets := map[string]sets.Set[string]{} + folder := cases.Fold() + for key := range blobMap { + foldKey := folder.String(key) + if _, ok := keySets[foldKey]; !ok { + keySets[foldKey] = sets.New[string]() + } + keySets[foldKey].Insert(key) } - var t tmp - if err := json.Unmarshal(blob, &t); err != nil { - // TODO: return an error that includes the the full JSON message, - // the offset of the error, and the error message. Let callers - // decide how to format it. - return errors.New(resolveUnmarshalErr(blob, err)) + + dupErrs := []error{} + for foldedKey, keys := range keySets { + if len(keys) != 1 { + dupErrs = append(dupErrs, fmt.Errorf("duplicate keys for key %q: %v", foldedKey, sets.List(keys))) + } + } + if len(dupErrs) > 0 { + return utilerrors.NewAggregate(dupErrs) + } + + metaMap := map[string]*string{ + folder.String("schema"): &m.Schema, + folder.String("package"): &m.Package, + folder.String("name"): &m.Name, + } + + for foldedKey, ptr := range metaMap { + // if the folded key doesn't exist in the key set derived from the blobMap, that means + // the key doesn't exist in the blobMap, so we can skip it + if _, ok := keySets[foldedKey]; !ok { + continue + } + + // reset key to the unfolded key, which we know is the one that appears in the blobMap + key := keySets[foldedKey].UnsortedList()[0] + if _, ok := blobMap[key]; !ok { + continue + } + v, ok := blobMap[key].(string) + if !ok { + return fmt.Errorf("expected value for key %q to be a string, got %t: %v", key, blobMap[key], blobMap[key]) + } + *ptr = v } - m.Schema = t.Schema - m.Package = t.Package - m.Name = t.Name - m.Blob = blob return nil } diff --git a/alpha/declcfg/load.go b/alpha/declcfg/load.go index 1f723bcc5..57476abe2 100644 --- a/alpha/declcfg/load.go +++ b/alpha/declcfg/load.go @@ -1,15 +1,19 @@ package declcfg import ( + "context" "encoding/json" "errors" "fmt" "io" "io/fs" "path/filepath" + "runtime" + "sync" "github.com/joelanford/ignore" "github.com/operator-framework/api/pkg/operators" + "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" @@ -111,20 +115,109 @@ func walkFiles(root fs.FS, fn func(root fs.FS, path string, err error) error) er // If LoadFS encounters an error loading or parsing any file, the error will be // immediately returned. func LoadFS(root fs.FS) (*DeclarativeConfig, error) { - cfg := &DeclarativeConfig{} - if err := WalkFS(root, func(path string, fcfg *DeclarativeConfig, err error) error { + if root == nil { + return nil, fmt.Errorf("no declarative config filesystem provided") + } + + concurrency := runtime.NumCPU() + + var ( + fcfg = &DeclarativeConfig{} + pathChan = make(chan string, concurrency) + cfgChan = make(chan *DeclarativeConfig, concurrency) + ) + + // Create an errgroup to manage goroutines. The context is closed when any + // goroutine returns an error. Goroutines should check the context + // to see if they should return early (in the case of another goroutine + // returning an error). + eg, ctx := errgroup.WithContext(context.Background()) + + // Walk the FS and send paths to a channel for parsing. + eg.Go(func() error { + return sendPaths(ctx, root, pathChan) + }) + + // Parse paths concurrently. The waitgroup ensures that all paths are parsed + // before the cfgChan is closed. + var wg sync.WaitGroup + for i := 0; i < concurrency; i++ { + wg.Add(1) + eg.Go(func() error { + defer wg.Done() + return parsePaths(ctx, root, pathChan, cfgChan) + }) + } + + // Merge parsed configs into a single config. + eg.Go(func() error { + return mergeCfgs(ctx, cfgChan, fcfg) + }) + + // Wait for all path parsing goroutines to finish before closing cfgChan. + wg.Wait() + close(cfgChan) + + // Wait for all goroutines to finish. + if err := eg.Wait(); err != nil { + return nil, err + } + return fcfg, nil +} + +func sendPaths(ctx context.Context, root fs.FS, pathChan chan<- string) error { + defer close(pathChan) + return walkFiles(root, func(_ fs.FS, path string, err error) error { if err != nil { return err } - cfg.Packages = append(cfg.Packages, fcfg.Packages...) - cfg.Channels = append(cfg.Channels, fcfg.Channels...) - cfg.Bundles = append(cfg.Bundles, fcfg.Bundles...) - cfg.Others = append(cfg.Others, fcfg.Others...) + select { + case pathChan <- path: + case <-ctx.Done(): // don't block on sending to pathChan + return ctx.Err() + } return nil - }); err != nil { - return nil, err + }) +} + +func parsePaths(ctx context.Context, root fs.FS, pathChan <-chan string, cfgChan chan<- *DeclarativeConfig) error { + for { + select { + case <-ctx.Done(): // don't block on receiving from pathChan + return ctx.Err() + case path, ok := <-pathChan: + if !ok { + return nil + } + cfg, err := LoadFile(root, path) + if err != nil { + return err + } + select { + case cfgChan <- cfg: + case <-ctx.Done(): // don't block on sending to cfgChan + return ctx.Err() + } + } + } +} + +func mergeCfgs(ctx context.Context, cfgChan <-chan *DeclarativeConfig, fcfg *DeclarativeConfig) error { + for { + select { + case <-ctx.Done(): // don't block on receiving from cfgChan + return ctx.Err() + case cfg, ok := <-cfgChan: + if !ok { + return nil + } + fcfg.Packages = append(fcfg.Packages, cfg.Packages...) + fcfg.Channels = append(fcfg.Channels, cfg.Channels...) + fcfg.Bundles = append(fcfg.Bundles, cfg.Bundles...) + fcfg.Others = append(fcfg.Others, cfg.Others...) + } + } - return cfg, nil } func readBundleObjects(bundles []Bundle, root fs.FS, path string) error { diff --git a/alpha/declcfg/load_benchmark_test.go b/alpha/declcfg/load_benchmark_test.go new file mode 100644 index 000000000..3f506b204 --- /dev/null +++ b/alpha/declcfg/load_benchmark_test.go @@ -0,0 +1,292 @@ +package declcfg_test + +import ( + "encoding/base64" + "fmt" + "math/rand" + "os" + "testing" + + "github.com/blang/semver/v4" + "github.com/operator-framework/api/pkg/lib/version" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" +) + +func BenchmarkLoadFS(b *testing.B) { + fbc := generateFBC(b, 300, 450, 3000) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + b.StopTimer() + tempDir := b.TempDir() + if err := declcfg.WriteFS(*fbc, tempDir, declcfg.WriteJSON, ".json"); err != nil { + b.Error(err) + } + b.StartTimer() + + _, err := declcfg.LoadFS(os.DirFS(tempDir)) + if err != nil { + b.Error(err) + } + } +} + +const randomPng = "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" + +func generateFBC(b *testing.B, numPackages, numChannels, numBundles int) *declcfg.DeclarativeConfig { + pngData, err := base64.StdEncoding.DecodeString(randomPng) + if err != nil { + b.Error(err) + } + + fbc := &declcfg.DeclarativeConfig{} + + for i := 0; i < numPackages; i++ { + pkgName := fmt.Sprintf("pkg-%d", i) + fbc.Packages = append(fbc.Packages, declcfg.Package{ + Schema: declcfg.SchemaPackage, + Name: pkgName, + Description: fmt.Sprintf("%s description", pkgName), + Icon: &declcfg.Icon{ + Data: pngData, + MediaType: "image/png", + }, + }) + } + for i := 0; i < numChannels; i++ { + pkgName := fbc.Packages[rand.Intn(numPackages)].Name + channelName := fmt.Sprintf("channel-%d", i) + fbc.Channels = append(fbc.Channels, declcfg.Channel{ + Schema: declcfg.SchemaChannel, + Package: pkgName, + Name: channelName, + }) + } + for i := 0; i < numBundles; i++ { + pkgName := fbc.Packages[rand.Intn(numPackages)].Name + bundleName := fmt.Sprintf("bundle-%d", i) + version := fmt.Sprintf("0.%d.0", i) + bundle := declcfg.Bundle{ + Schema: declcfg.SchemaBundle, + Package: pkgName, + Image: fmt.Sprintf("bundles/%s", bundleName), + Properties: []property.Property{ + property.MustBuildPackage(pkgName, version), + property.MustBuildGVK("apps", "Deployment", "v1"), + property.MustBuildGVK("apps", "DaemonSet", "v1"), + property.MustBuildGVKRequired("", "Service", "v1"), + property.MustBuildPackageRequired("foo", "0.0.1"), + }, + } + + csv := genCsv(pkgName, version) + bundle.Properties = append(bundle.Properties, property.MustBuildCSVMetadata(csv)) + fbc.Bundles = append(fbc.Bundles, bundle) + + chIdx := rand.Intn(numChannels) + ch := fbc.Channels[chIdx] + replaces := "" + if len(ch.Entries) > 0 { + replaces = ch.Entries[len(ch.Entries)-1].Name + } + ch.Entries = append(ch.Entries, declcfg.ChannelEntry{ + Name: bundleName, + Replaces: replaces, + }) + fbc.Channels[chIdx] = ch + } + + return fbc +} + +func genCsv(pkgName, ver string) v1alpha1.ClusterServiceVersion { + csv := v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: "operators.coreos.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s.v%s", pkgName, ver), + Annotations: map[string]string{ + "alm-examples": `[ + { + "apiVersion": "example.com/v1alpha1", + "kind": "Example", + "metadata": { + "name": "example-sample" + }, + "spec": { + "image":{ + "repository":"example/example", + "tag":"v0.0.1", + "pullPolicy":"IfNotPresent", + "credentials":{} + }, + "labels":{}, + "conf":{ + "log":{"enabled":false,"config":{}}, + "logtostderr":"INFO", + "port":26257, + "http-port":8080, + "store":{"enabled":false,"type":null,"size":null,"attrs":null}}, + "statefulset":{ + "replicas":3, + "updateStrategy":{"type":"RollingUpdate"}, + "podManagementPolicy":"Parallel", + "budget":{"maxUnavailable":1}, + "args":[], + "env":[], + "secretMounts":[], + "labels":{"app.kubernetes.io/component":"example"}, + "annotations":{}, + "nodeAffinity":{}, + "podAffinity":{}, + "podAntiAffinity":{"topologyKey":"kubernetes.io/hostname","type":"soft","weight":100}, + "nodeSelector":{}, + "priorityClassName":"", + "tolerations":[], + "topologySpreadConstraints":{"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone","whenUnsatisfiable":"ScheduleAnyway"}, + "resources":{}, + "customLivenessProbe":{}, + "customReadinessProbe":{} + }, + "ingress":{"enabled":false,"labels":{},"annotations":{},"paths":["/"],"hosts":[],"tls":[]}, + "prometheus":{"enabled":true}, + "serviceMonitor":{"enabled":false,"labels":{},"annotations":{},"interval":"10s","namespaced":false}, + "storage":{"hostPath":"","persistentVolume":{"enabled":true,"size":"100Gi","storageClass":"","labels":{},"annotations":{}}} + } + } + } + ]`, + "capabilities": "Basic Install", + "categories": "Application", + "certified": "false", + "containerImage": "quay.io/example/example:v0.0.1", + "createdAt": "1970-01-01T00-00-00Z", + "description": "Example Operator", + "operators.operatorframework.io/builder": "operator-sdk-v1.0.0", + "operators.operatorframework.io/project_layout": "go.sdk.operatorframework.io/v3", + "repository": "https://github.com/example/example", + "support": "Example, Inc.", + "olm.skipRange": "<0.0.1", + }, + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Icon: []v1alpha1.Icon{ + { + Data: randomPng, + MediaType: "image/png", + }, + }, + CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{ + Owned: []v1alpha1.CRDDescription{ + { + Description: "Represents an Example instance", + DisplayName: "Example", + Kind: "Example", + Version: "v1alpha1", + Name: "examples.example.com", + Resources: []v1alpha1.APIResourceReference{ + { + Kind: "Deployment", + Version: "apps/v1", + }, + { + Kind: "Service", + Version: "v1", + }, + { + Kind: "ReplicaSet", + Version: "apps/v1", + }, + { + Kind: "Pod", + Version: "v1", + }, + { + Kind: "Secret", + Version: "v1", + }, + { + Kind: "ConfigMap", + Version: "v1", + }, + { + Kind: "PresistentVolumeClaim", + Version: "v1", + }, + { + Kind: "StatefulSet", + Version: "apps/v1", + }, + { + Kind: "Job", + Version: "batch/v1", + }, + }, + }, + }, + }, + DisplayName: "Example Operator", + InstallModes: []v1alpha1.InstallMode{ + { + Type: v1alpha1.InstallModeTypeOwnNamespace, + Supported: false, + }, + { + Type: v1alpha1.InstallModeTypeSingleNamespace, + Supported: false, + }, + { + Type: v1alpha1.InstallModeTypeMultiNamespace, + Supported: false, + }, + { + Type: v1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + Keywords: []string{"example", "application", "sample", "foobar"}, + Links: []v1alpha1.AppLink{ + { + Name: "Example Operator", + URL: "https://www.example.com/operator", + }, + { + Name: "Example Operator Documentation", + URL: "https://www.example.com/operator/docs", + }, + { + Name: "Example Operator Support", + URL: "https://www.example.com/operator/support", + }, + { + Name: "Example Operator Source Code", + URL: "https://github.com/example/operator", + }, + }, + Maintainers: []v1alpha1.Maintainer{ + { + Email: "janedoe@example.com", + Name: "Jane Doe", + }, + { + Email: "johndoe@example.com", + Name: "John Doe", + }, + }, + Maturity: "alpha", + MinKubeVersion: "1.21.0", + Provider: v1alpha1.AppLink{ + Name: "Example, Inc.", + URL: "https://www.example.com", + }, + Version: version.OperatorVersion{Version: semver.MustParse(ver)}, + }, + } + return csv +} diff --git a/alpha/declcfg/write.go b/alpha/declcfg/write.go index c2d869905..2f149e5af 100644 --- a/alpha/declcfg/write.go +++ b/alpha/declcfg/write.go @@ -6,13 +6,15 @@ import ( "fmt" "io" "os" + "path/filepath" "sort" "strings" "github.com/blang/semver/v4" - "github.com/operator-framework/operator-registry/alpha/property" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-registry/alpha/property" ) type MermaidWriter struct { @@ -425,3 +427,48 @@ func writeToEncoder(cfg DeclarativeConfig, enc encoder) error { } return nil } + +type WriteFunc func(config DeclarativeConfig, w io.Writer) error + +func WriteFS(cfg DeclarativeConfig, rootDir string, writeFunc WriteFunc, fileExt string) error { + channelsByPackage := map[string][]Channel{} + for _, c := range cfg.Channels { + channelsByPackage[c.Package] = append(channelsByPackage[c.Package], c) + } + bundlesByPackage := map[string][]Bundle{} + for _, b := range cfg.Bundles { + bundlesByPackage[b.Package] = append(bundlesByPackage[b.Package], b) + } + + if err := os.MkdirAll(rootDir, 0777); err != nil { + return err + } + + for _, p := range cfg.Packages { + fcfg := DeclarativeConfig{ + Packages: []Package{p}, + Channels: channelsByPackage[p.Name], + Bundles: bundlesByPackage[p.Name], + } + pkgDir := filepath.Join(rootDir, p.Name) + if err := os.MkdirAll(pkgDir, 0777); err != nil { + return err + } + filename := filepath.Join(pkgDir, fmt.Sprintf("catalog%s", fileExt)) + if err := writeFile(fcfg, filename, writeFunc); err != nil { + return err + } + } + return nil +} + +func writeFile(cfg DeclarativeConfig, filename string, writeFunc WriteFunc) error { + buf := &bytes.Buffer{} + if err := writeFunc(cfg, buf); err != nil { + return fmt.Errorf("write to buffer for %q: %v", filename, err) + } + if err := os.WriteFile(filename, buf.Bytes(), 0666); err != nil { + return fmt.Errorf("write file %q: %v", filename, err) + } + return nil +} diff --git a/alpha/property/property.go b/alpha/property/property.go index 9ccd0f14b..5cb876846 100644 --- a/alpha/property/property.go +++ b/alpha/property/property.go @@ -9,6 +9,9 @@ import ( "io/ioutil" "path/filepath" "reflect" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Property struct { @@ -70,6 +73,23 @@ type BundleObject struct { File `json:",inline"` } +type CSVMetadata struct { + Annotations map[string]string `json:"annotations,omitempty"` + APIServiceDefinitions v1alpha1.APIServiceDefinitions `json:"apiServiceDefinitions,omitempty"` + CustomResourceDefinitions v1alpha1.CustomResourceDefinitions `json:"crdDescriptions,omitempty"` + Description string `json:"description,omitempty"` + DisplayName string `json:"displayName,omitempty"` + InstallModes []v1alpha1.InstallMode `json:"installModes,omitempty"` + Keywords []string `json:"keywords,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Links []v1alpha1.AppLink `json:"links,omitempty"` + Maintainers []v1alpha1.Maintainer `json:"maintainers,omitempty"` + Maturity string `json:"maturity,omitempty"` + MinKubeVersion string `json:"minKubeVersion,omitempty"` + NativeAPIs []metav1.GroupVersionKind `json:"nativeAPIs,omitempty"` + Provider v1alpha1.AppLink `json:"provider,omitempty"` +} + type File struct { ref string data []byte @@ -129,6 +149,7 @@ type Properties struct { GVKsRequired []GVKRequired `hash:"set"` BundleObjects []BundleObject `hash:"set"` Channels []Channel `hash:"set"` + CSVMetadatas []CSVMetadata `hash:"set"` Others []Property `hash:"set"` } @@ -139,6 +160,7 @@ const ( TypeGVK = "olm.gvk" TypeGVKRequired = "olm.gvk.required" TypeBundleObject = "olm.bundle.object" + TypeCSVMetadata = "olm.csv.metadata" TypeChannel = "olm.channel" ) @@ -176,6 +198,12 @@ func Parse(in []Property) (*Properties, error) { return nil, ParseError{Idx: i, Typ: prop.Type, Err: err} } out.BundleObjects = append(out.BundleObjects, p) + case TypeCSVMetadata: + var p CSVMetadata + if err := json.Unmarshal(prop.Value, &p); err != nil { + return nil, ParseError{Idx: i, Typ: prop.Type, Err: err} + } + out.CSVMetadatas = append(out.CSVMetadatas, p) // NOTICE: The Channel properties are for internal use only. // DO NOT use it for any public-facing functionalities. // This API is in alpha stage and it is subject to change. @@ -287,6 +315,25 @@ func MustBuildBundleObjectData(data []byte) Property { return MustBuild(&BundleObject{File: File{data: data}}) } +func MustBuildCSVMetadata(csv v1alpha1.ClusterServiceVersion) Property { + return MustBuild(&CSVMetadata{ + Annotations: csv.GetAnnotations(), + APIServiceDefinitions: csv.Spec.APIServiceDefinitions, + CustomResourceDefinitions: csv.Spec.CustomResourceDefinitions, + Description: csv.Spec.Description, + DisplayName: csv.Spec.DisplayName, + InstallModes: csv.Spec.InstallModes, + Keywords: csv.Spec.Keywords, + Labels: csv.GetLabels(), + Links: csv.Spec.Links, + Maintainers: csv.Spec.Maintainers, + Maturity: csv.Spec.Maturity, + MinKubeVersion: csv.Spec.MinKubeVersion, + NativeAPIs: csv.Spec.NativeAPIs, + Provider: csv.Spec.Provider, + }) +} + // NOTICE: The Channel properties are for internal use only. // // DO NOT use it for any public-facing functionalities. diff --git a/alpha/property/scheme.go b/alpha/property/scheme.go index 28d8a5d14..ab176856f 100644 --- a/alpha/property/scheme.go +++ b/alpha/property/scheme.go @@ -12,6 +12,7 @@ func init() { reflect.TypeOf(&GVK{}): TypeGVK, reflect.TypeOf(&GVKRequired{}): TypeGVKRequired, reflect.TypeOf(&BundleObject{}): TypeBundleObject, + reflect.TypeOf(&CSVMetadata{}): TypeCSVMetadata, // NOTICE: The Channel properties are for internal use only. // DO NOT use it for any public-facing functionalities. // This API is in alpha stage and it is subject to change. diff --git a/alpha/template/composite/builder_test.go b/alpha/template/composite/builder_test.go index 85538c18c..c4395ad54 100644 --- a/alpha/template/composite/builder_test.go +++ b/alpha/template/composite/builder_test.go @@ -8,8 +8,9 @@ import ( "path" "testing" - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" ) // TODO: Should we consolidate all these tests into a singular test function? @@ -293,15 +294,55 @@ properties: value: packageName: webhook-operator version: 0.0.1 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0= -- type: olm.bundle.object +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0= -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= + annotations: + alm-examples: |- + [ + { + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "WebhookTest", + "metadata": { + "name": "webhooktest-sample", + "namespace": "webhook-operator-system" + }, + "spec": { + "valid": true + } + } + ] + capabilities: Basic Install + operators.operatorframework.io/builder: operator-sdk-v1.0.0 + operators.operatorframework.io/project_layout: go + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: WebhookTest + name: webhooktests.webhook.operators.coreos.io + version: v1 + description: Webhook Operator description. TODO. + displayName: Webhook Operator + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - webhook-operator + links: + - name: Webhook Operator + url: https://webhook-operator.domain + maintainers: + - email: your@email.com + name: Maintainer Name + maturity: alpha + provider: + name: Provider Name + url: https://your.domain relatedImages: - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 name: "" @@ -357,21 +398,64 @@ const basicBuiltFbcJson = `{ } }, { - "type": "olm.bundle.object", + "type": "olm.csv.metadata", "value": { - "data": "eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0=" + "annotations": { + "alm-examples": "[\n {\n \"apiVersion\": \"webhook.operators.coreos.io/v1\",\n \"kind\": \"WebhookTest\",\n \"metadata\": {\n \"name\": \"webhooktest-sample\",\n \"namespace\": \"webhook-operator-system\"\n },\n \"spec\": {\n \"valid\": true\n }\n }\n]", + "capabilities": "Basic Install", + "operators.operatorframework.io/builder": "operator-sdk-v1.0.0", + "operators.operatorframework.io/project_layout": "go" + }, + "apiServiceDefinitions": {}, + "crdDescriptions": { + "owned": [ + { + "name": "webhooktests.webhook.operators.coreos.io", + "version": "v1", + "kind": "WebhookTest" + } + ] + }, + "description": "Webhook Operator description. TODO.", + "displayName": "Webhook Operator", + "installModes": [ + { + "type": "OwnNamespace", + "supported": false + }, + { + "type": "SingleNamespace", + "supported": false + }, + { + "type": "MultiNamespace", + "supported": false + }, + { + "type": "AllNamespaces", + "supported": true + } + ], + "keywords": [ + "webhook-operator" + ], + "links": [ + { + "name": "Webhook Operator", + "url": "https://webhook-operator.domain" + } + ], + "maintainers": [ + { + "name": "Maintainer Name", + "email": "your@email.com" + } + ], + "maturity": "alpha", + "provider": { + "name": "Provider Name", + "url": "https://your.domain" + } } } ], @@ -673,15 +757,55 @@ properties: 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 +- type: olm.csv.metadata value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= + annotations: + alm-examples: |- + [ + { + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "WebhookTest", + "metadata": { + "name": "webhooktest-sample", + "namespace": "webhook-operator-system" + }, + "spec": { + "valid": true + } + } + ] + capabilities: Basic Install + operators.operatorframework.io/builder: operator-sdk-v1.0.0 + operators.operatorframework.io/project_layout: go + apiServiceDefinitions: {} + crdDescriptions: + owned: + - kind: WebhookTest + name: webhooktests.webhook.operators.coreos.io + version: v1 + description: Webhook Operator description. TODO. + displayName: Webhook Operator + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - webhook-operator + links: + - name: Webhook Operator + url: https://webhook-operator.domain + maintainers: + - email: your@email.com + name: Maintainer Name + maturity: alpha + provider: + name: Provider Name + url: https://your.domain relatedImages: - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 name: "" @@ -747,21 +871,64 @@ const semverBuiltFbcJson = `{ } }, { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MWJldGExIiwia2luZCI6IkNsdXN0ZXJSb2xlIiwibWV0YWRhdGEiOnsiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLW1ldHJpY3MtcmVhZGVyIn0sInJ1bGVzIjpbeyJub25SZXNvdXJjZVVSTHMiOlsiL21ldHJpY3MiXSwidmVyYnMiOlsiZ2V0Il19XX0=" - } - }, - { - "type": "olm.bundle.object", - "value": { - "data": "eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvL3YxXCIsXG4gICAgXCJraW5kXCI6IFwiV2ViaG9va1Rlc3RcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcIndlYmhvb2t0ZXN0LXNhbXBsZVwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJ3ZWJob29rLW9wZXJhdG9yLXN5c3RlbVwiXG4gICAgfSxcbiAgICBcInNwZWNcIjoge1xuICAgICAgXCJ2YWxpZFwiOiB0cnVlXG4gICAgfVxuICB9XG5dIiwiY2FwYWJpbGl0aWVzIjoiQmFzaWMgSW5zdGFsbCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9idWlsZGVyIjoib3BlcmF0b3Itc2RrLXYxLjAuMCIsIm9wZXJhdG9ycy5vcGVyYXRvcmZyYW1ld29yay5pby9wcm9qZWN0X2xheW91dCI6ImdvIn0sIm5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IldlYmhvb2tUZXN0IiwibmFtZSI6IndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iLCJ2ZXJzaW9uIjoidjEifV19LCJkZXNjcmlwdGlvbiI6IldlYmhvb2sgT3BlcmF0b3IgZGVzY3JpcHRpb24uIFRPRE8uIiwiZGlzcGxheU5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJ3ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwicmVzb3VyY2VzIjpbIndlYmhvb2t0ZXN0cy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJkZWZhdWx0In1dLCJkZXBsb3ltZW50cyI6W3sibmFtZSI6IndlYmhvb2stb3BlcmF0b3Itd2ViaG9vayIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjUuMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIn1dLCJyZXNvdXJjZXMiOnt9fSx7ImFyZ3MiOlsiLS1tZXRyaWNzLWFkZHI9MTI3LjAuMC4xOjgwODAiLCItLWVuYWJsZS1sZWFkZXItZWxlY3Rpb24iXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vb2xtdGVzdC93ZWJob29rLW9wZXJhdG9yOjAuMC4zIiwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6OTQ0MywibmFtZSI6IndlYmhvb2stc2VydmVyIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjMwTWkifSwicmVxdWVzdHMiOnsiY3B1IjoiMTAwbSIsIm1lbW9yeSI6IjIwTWkifX19XSwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInVwZGF0ZSIsInBhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImRlZmF1bHQifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbIndlYmhvb2stb3BlcmF0b3IiXSwibGlua3MiOlt7Im5hbWUiOiJXZWJob29rIE9wZXJhdG9yIiwidXJsIjoiaHR0cHM6Ly93ZWJob29rLW9wZXJhdG9yLmRvbWFpbiJ9XSwibWFpbnRhaW5lcnMiOlt7ImVtYWlsIjoieW91ckBlbWFpbC5jb20iLCJuYW1lIjoiTWFpbnRhaW5lciBOYW1lIn1dLCJtYXR1cml0eSI6ImFscGhhIiwicHJvdmlkZXIiOnsibmFtZSI6IlByb3ZpZGVyIE5hbWUiLCJ1cmwiOiJodHRwczovL3lvdXIuZG9tYWluIn0sInZlcnNpb24iOiIwLjAuMSIsIndlYmhvb2tkZWZpbml0aW9ucyI6W3siYWRtaXNzaW9uUmV2aWV3VmVyc2lvbnMiOlsidjFiZXRhMSIsInYxIl0sImNvbnRhaW5lclBvcnQiOjQ0MywiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6InZ3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiVmFsaWRhdGluZ0FkbWlzc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii92YWxpZGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImRlcGxveW1lbnROYW1lIjoid2ViaG9vay1vcGVyYXRvci13ZWJob29rIiwiZmFpbHVyZVBvbGljeSI6IkZhaWwiLCJnZW5lcmF0ZU5hbWUiOiJtd2ViaG9va3Rlc3Qua2IuaW8iLCJydWxlcyI6W3siYXBpR3JvdXBzIjpbIndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJdLCJhcGlWZXJzaW9ucyI6WyJ2MSJdLCJvcGVyYXRpb25zIjpbIkNSRUFURSIsIlVQREFURSJdLCJyZXNvdXJjZXMiOlsid2ViaG9va3Rlc3RzIl19XSwic2lkZUVmZmVjdHMiOiJOb25lIiwidGFyZ2V0UG9ydCI6NDM0MywidHlwZSI6Ik11dGF0aW5nQWRtaXNzaW9uV2ViaG9vayIsIndlYmhvb2tQYXRoIjoiL211dGF0ZS13ZWJob29rLW9wZXJhdG9ycy1jb3Jlb3MtaW8tdjEtd2ViaG9va3Rlc3QifSx7ImFkbWlzc2lvblJldmlld1ZlcnNpb25zIjpbInYxYmV0YTEiLCJ2MSJdLCJjb250YWluZXJQb3J0Ijo0NDMsImNvbnZlcnNpb25DUkRzIjpbIndlYmhvb2t0ZXN0cy53ZWJob29rLm9wZXJhdG9ycy5jb3Jlb3MuaW8iXSwiZGVwbG95bWVudE5hbWUiOiJ3ZWJob29rLW9wZXJhdG9yLXdlYmhvb2siLCJmYWlsdXJlUG9saWN5IjoiRmFpbCIsImdlbmVyYXRlTmFtZSI6ImN3ZWJob29rdGVzdC5rYi5pbyIsInJ1bGVzIjpbeyJhcGlHcm91cHMiOlsid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIl0sImFwaVZlcnNpb25zIjpbInYxIl0sIm9wZXJhdGlvbnMiOlsiQ1JFQVRFIiwiVVBEQVRFIl0sInJlc291cmNlcyI6WyJ3ZWJob29rdGVzdHMiXX1dLCJzaWRlRWZmZWN0cyI6Ik5vbmUiLCJ0YXJnZXRQb3J0Ijo0MzQzLCJ0eXBlIjoiQ29udmVyc2lvbldlYmhvb2siLCJ3ZWJob29rUGF0aCI6Ii9jb252ZXJ0In1dfX0=" - } - }, - { - "type": "olm.bundle.object", + "type": "olm.csv.metadata", "value": { - "data": "eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjFiZXRhMSIsImtpbmQiOiJDdXN0b21SZXNvdXJjZURlZmluaXRpb24iLCJtZXRhZGF0YSI6eyJhbm5vdGF0aW9ucyI6eyJjb250cm9sbGVyLWdlbi5rdWJlYnVpbGRlci5pby92ZXJzaW9uIjoidjAuMy4wIn0sImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoid2ViaG9va3Rlc3RzLndlYmhvb2sub3BlcmF0b3JzLmNvcmVvcy5pbyJ9LCJzcGVjIjp7Imdyb3VwIjoid2ViaG9vay5vcGVyYXRvcnMuY29yZW9zLmlvIiwibmFtZXMiOnsia2luZCI6IldlYmhvb2tUZXN0IiwibGlzdEtpbmQiOiJXZWJob29rVGVzdExpc3QiLCJwbHVyYWwiOiJ3ZWJob29rdGVzdHMiLCJzaW5ndWxhciI6IndlYmhvb2t0ZXN0In0sInByZXNlcnZlVW5rbm93bkZpZWxkcyI6ZmFsc2UsInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb24iOiJ2MSIsInZlcnNpb25zIjpbeyJuYW1lIjoidjEiLCJzY2hlbWEiOnsib3BlbkFQSVYzU2NoZW1hIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3QgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIHdlYmhvb2t0ZXN0cyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwicHJvcGVydGllcyI6eyJtdXRhdGUiOnsiZGVzY3JpcHRpb24iOiJNdXRhdGUgaXMgYSBmaWVsZCB0aGF0IHdpbGwgYmUgc2V0IHRvIHRydWUgYnkgdGhlIG11dGF0aW5nIHdlYmhvb2suIiwidHlwZSI6ImJvb2xlYW4ifSwidmFsaWQiOnsiZGVzY3JpcHRpb24iOiJWYWxpZCBtdXN0IGJlIHNldCB0byB0cnVlIG9yIHRoZSB2YWxpZGF0aW9uIHdlYmhvb2sgd2lsbCByZWplY3QgdGhlIHJlc291cmNlLiIsInR5cGUiOiJib29sZWFuIn19LCJyZXF1aXJlZCI6WyJ2YWxpZCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfSx7Im5hbWUiOiJ2MiIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJXZWJob29rVGVzdCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgd2ViaG9va3Rlc3RzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC4gU2VydmVycyBzaG91bGQgY29udmVydCByZWNvZ25pemVkIHNjaGVtYXMgdG8gdGhlIGxhdGVzdCBpbnRlcm5hbCB2YWx1ZSwgYW5kIG1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLiBTZXJ2ZXJzIG1heSBpbmZlciB0aGlzIGZyb20gdGhlIGVuZHBvaW50IHRoZSBjbGllbnQgc3VibWl0cyByZXF1ZXN0cyB0by4gQ2Fubm90IGJlIHVwZGF0ZWQuIEluIENhbWVsQ2FzZS4gTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCN0eXBlcy1raW5kcyIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsidHlwZSI6Im9iamVjdCJ9LCJzcGVjIjp7ImRlc2NyaXB0aW9uIjoiV2ViaG9va1Rlc3RTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgV2ViaG9va1Rlc3QiLCJwcm9wZXJ0aWVzIjp7ImNvbnZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJDb252ZXJzaW9uIGlzIGFuIGV4YW1wbGUgZmllbGQgb2YgV2ViaG9va1Rlc3QuIEVkaXQgV2ViaG9va1Rlc3RfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInByb3BlcnRpZXMiOnsibXV0YXRlIjp7ImRlc2NyaXB0aW9uIjoiTXV0YXRlIGlzIGEgZmllbGQgdGhhdCB3aWxsIGJlIHNldCB0byB0cnVlIGJ5IHRoZSBtdXRhdGluZyB3ZWJob29rLiIsInR5cGUiOiJib29sZWFuIn0sInZhbGlkIjp7ImRlc2NyaXB0aW9uIjoiVmFsaWQgbXVzdCBiZSBzZXQgdG8gdHJ1ZSBvciB0aGUgdmFsaWRhdGlvbiB3ZWJob29rIHdpbGwgcmVqZWN0IHRoZSByZXNvdXJjZS4iLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsidmFsaWQiXSwidHlwZSI6Im9iamVjdCJ9fSwicmVxdWlyZWQiOlsiY29udmVyc2lvbiJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IldlYmhvb2tUZXN0U3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIFdlYmhvb2tUZXN0IiwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjpmYWxzZX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0=" + "annotations": { + "alm-examples": "[\n {\n \"apiVersion\": \"webhook.operators.coreos.io/v1\",\n \"kind\": \"WebhookTest\",\n \"metadata\": {\n \"name\": \"webhooktest-sample\",\n \"namespace\": \"webhook-operator-system\"\n },\n \"spec\": {\n \"valid\": true\n }\n }\n]", + "capabilities": "Basic Install", + "operators.operatorframework.io/builder": "operator-sdk-v1.0.0", + "operators.operatorframework.io/project_layout": "go" + }, + "apiServiceDefinitions": {}, + "crdDescriptions": { + "owned": [ + { + "name": "webhooktests.webhook.operators.coreos.io", + "version": "v1", + "kind": "WebhookTest" + } + ] + }, + "description": "Webhook Operator description. TODO.", + "displayName": "Webhook Operator", + "installModes": [ + { + "type": "OwnNamespace", + "supported": false + }, + { + "type": "SingleNamespace", + "supported": false + }, + { + "type": "MultiNamespace", + "supported": false + }, + { + "type": "AllNamespaces", + "supported": true + } + ], + "keywords": [ + "webhook-operator" + ], + "links": [ + { + "name": "Webhook Operator", + "url": "https://webhook-operator.domain" + } + ], + "maintainers": [ + { + "name": "Maintainer Name", + "email": "your@email.com" + } + ], + "maturity": "alpha", + "provider": { + "name": "Provider Name", + "url": "https://your.domain" + } } } ], diff --git a/go.mod b/go.mod index 8772a2d1a..18d41407f 100644 --- a/go.mod +++ b/go.mod @@ -31,11 +31,11 @@ require ( github.com/spf13/cobra v1.6.0 github.com/stretchr/testify v1.8.0 go.etcd.io/bbolt v1.3.6 - go4.org v0.0.0-20230225012048-214862532bf5 golang.org/x/mod v0.6.0 golang.org/x/net v0.7.0 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/sys v0.5.0 + golang.org/x/text v0.7.0 google.golang.org/grpc v1.49.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200709232328-d8193ee9cc3e google.golang.org/protobuf v1.28.1 @@ -149,7 +149,6 @@ require ( golang.org/x/crypto v0.1.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 77209b6ab..8f1398c83 100644 --- a/go.sum +++ b/go.sum @@ -654,7 +654,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= @@ -722,7 +721,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= @@ -775,8 +773,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= -go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -789,7 +785,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -823,7 +818,6 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -872,7 +866,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -964,8 +957,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1040,7 +1031,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/api/api_to_model.go b/pkg/api/api_to_model.go index d203f40b4..06645f7c6 100644 --- a/pkg/api/api_to_model.go +++ b/pkg/api/api_to_model.go @@ -5,6 +5,8 @@ import ( "fmt" "sort" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" ) @@ -106,8 +108,17 @@ func convertAPIBundleToModelProperties(b *Bundle) ([]property.Property, error) { out = append(out, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) } - for _, obj := range b.Object { - out = append(out, property.MustBuildBundleObjectData([]byte(obj))) + // If there is a bundle image reference and a valid CSV, create an + // olm.csv.metadata property. Otherwise, create a bundle object property for + // each object in the bundle. + var csv v1alpha1.ClusterServiceVersion + csvErr := json.Unmarshal([]byte(b.CsvJson), &csv) + if csvErr == nil && b.BundlePath != "" { + out = append(out, property.MustBuildCSVMetadata(csv)) + } else { + for _, obj := range b.Object { + out = append(out, property.MustBuildBundleObjectData([]byte(obj))) + } } sort.Slice(out, func(i, j int) bool { diff --git a/pkg/api/conversion_test.go b/pkg/api/conversion_test.go index 0a332b91a..55dc94904 100644 --- a/pkg/api/conversion_test.go +++ b/pkg/api/conversion_test.go @@ -1,8 +1,10 @@ package api import ( + "encoding/json" "testing" + "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -12,7 +14,7 @@ import ( func TestConvertAPIBundleToModelBundle(t *testing.T) { apiBundle := testAPIBundle() - expected := testModelBundle() + expected := testModelBundle(t) actual, err := ConvertAPIBundleToModelBundle(&apiBundle) require.NoError(t, err) @@ -20,7 +22,7 @@ func TestConvertAPIBundleToModelBundle(t *testing.T) { } func TestConvertModelBundleToAPIBundle(t *testing.T) { - modelBundle := testModelBundle() + modelBundle := testModelBundle(t) modelBundle.Package = &model.Package{Name: "etcd"} modelBundle.Channel = &model.Channel{Name: "singlenamespace-alpha"} expected := testAPIBundle() @@ -41,7 +43,12 @@ const ( crdrestores = `{"apiVersion":"apiextensions.k8s.io/v1beta1","kind":"CustomResourceDefinition","metadata":{"name":"etcdrestores.etcd.database.coreos.com"},"spec":{"group":"etcd.database.coreos.com","names":{"kind":"EtcdRestore","listKind":"EtcdRestoreList","plural":"etcdrestores","singular":"etcdrestore"},"scope":"Namespaced","version":"v1beta2"}}` ) -func testModelBundle() model.Bundle { +func testModelBundle(t *testing.T) model.Bundle { + t.Helper() + var csv v1alpha1.ClusterServiceVersion + if err := json.Unmarshal([]byte(csvJson), &csv); err != nil { + t.Fatalf("failed to unmarshal csv json: %v", err) + } b := model.Bundle{ Name: "etcdoperator.v0.9.4", Image: "quay.io/operatorhubio/etcd:v0.9.4", @@ -52,10 +59,7 @@ func testModelBundle() model.Bundle { property.MustBuildPackageRequired("test", ">=1.2.3 <2.0.0-0"), property.MustBuildGVKRequired("testapi.coreos.com", "v1", "Testapi"), property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), - property.MustBuildBundleObjectData([]byte(crdbackups)), - property.MustBuildBundleObjectData([]byte(crdclusters)), - property.MustBuildBundleObjectData([]byte(csvJson)), - property.MustBuildBundleObjectData([]byte(crdrestores)), + property.MustBuildCSVMetadata(csv), }, CsvJSON: csvJson, Objects: []string{ diff --git a/pkg/api/model_to_api.go b/pkg/api/model_to_api.go index 26f8b745c..1abe1cf6b 100644 --- a/pkg/api/model_to_api.go +++ b/pkg/api/model_to_api.go @@ -1,9 +1,15 @@ package api import ( + "encoding/base64" "encoding/json" "fmt" + "github.com/operator-framework/api/pkg/lib/version" + "github.com/operator-framework/api/pkg/operators" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" ) @@ -14,6 +20,38 @@ func ConvertModelBundleToAPIBundle(b model.Bundle) (*Bundle, error) { return nil, fmt.Errorf("parse properties: %v", err) } + csvJson := b.CsvJSON + if csvJson == "" && len(props.CSVMetadatas) == 1 { + var icons []v1alpha1.Icon + if b.Package.Icon != nil { + icons = []v1alpha1.Icon{{ + Data: base64.StdEncoding.EncodeToString(b.Package.Icon.Data), + MediaType: b.Package.Icon.MediaType, + }} + } + csv := csvMetadataToCsv(props.CSVMetadatas[0]) + csv.Name = b.Name + csv.Spec.Icon = icons + csv.Spec.InstallStrategy = v1alpha1.NamedInstallStrategy{ + // This stub is required to avoid a panic in OLM's package server that results in + // attemptint to write to a nil map. + StrategyName: "deployment", + } + csv.Spec.Version = version.OperatorVersion{b.Version} + csv.Spec.RelatedImages = convertModelRelatedImagesToCSVRelatedImages(b.RelatedImages) + if csv.Spec.Description == "" { + csv.Spec.Description = b.Package.Description + } + csvData, err := json.Marshal(csv) + if err != nil { + return nil, err + } + csvJson = string(csvData) + if len(b.Objects) == 0 { + b.Objects = []string{csvJson} + } + } + apiDeps, err := convertModelPropertiesToAPIDependencies(b.Properties) if err != nil { return nil, fmt.Errorf("convert model properties to api dependencies: %v", err) @@ -31,7 +69,7 @@ func ConvertModelBundleToAPIBundle(b model.Bundle) (*Bundle, error) { Properties: convertModelPropertiesToAPIProperties(b.Properties), Replaces: b.Replaces, Skips: b.Skips, - CsvJson: b.CsvJSON, + CsvJson: csvJson, Object: b.Objects, }, nil } @@ -46,9 +84,40 @@ func parseProperties(in []property.Property) (*property.Properties, error) { return nil, fmt.Errorf("expected exactly 1 property of type %q, found %d", property.TypePackage, len(props.Packages)) } + if len(props.CSVMetadatas) > 1 { + return nil, fmt.Errorf("expected at most 1 property of type %q, found %d", property.TypeCSVMetadata, len(props.CSVMetadatas)) + } + return props, nil } +func csvMetadataToCsv(m property.CSVMetadata) v1alpha1.ClusterServiceVersion { + return v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: operators.ClusterServiceVersionKind, + APIVersion: v1alpha1.ClusterServiceVersionAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: m.Annotations, + Labels: m.Labels, + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + APIServiceDefinitions: m.APIServiceDefinitions, + CustomResourceDefinitions: m.CustomResourceDefinitions, + Description: m.Description, + DisplayName: m.DisplayName, + InstallModes: m.InstallModes, + Keywords: m.Keywords, + Links: m.Links, + Maintainers: m.Maintainers, + Maturity: m.Maturity, + MinKubeVersion: m.MinKubeVersion, + NativeAPIs: m.NativeAPIs, + Provider: m.Provider, + }, + } +} + func gvksProvidedtoAPIGVKs(in []property.GVK) []*GroupVersionKind { var out []*GroupVersionKind for _, gvk := range in { @@ -82,7 +151,7 @@ func convertModelPropertiesToAPIProperties(props []property.Property) []*Propert // in its `Data` field, this CSV annotation projection would cause the size of the on-cluster // CSV to at least double, which is untenable since CSVs already have known issues running up // against etcd size constraints. - if prop.Type == property.TypeBundleObject { + if prop.Type == property.TypeBundleObject || prop.Type == property.TypeCSVMetadata { continue } @@ -117,3 +186,14 @@ func convertModelPropertiesToAPIDependencies(props []property.Property) ([]*Depe } return out, nil } + +func convertModelRelatedImagesToCSVRelatedImages(in []model.RelatedImage) []v1alpha1.RelatedImage { + var out []v1alpha1.RelatedImage + for _, ri := range in { + out = append(out, v1alpha1.RelatedImage{ + Name: ri.Name, + Image: ri.Image, + }) + } + return out +} diff --git a/pkg/registry/registry_to_model.go b/pkg/registry/registry_to_model.go index 14b6e7b00..869b8a5c7 100644 --- a/pkg/registry/registry_to_model.go +++ b/pkg/registry/registry_to_model.go @@ -5,6 +5,9 @@ import ( "fmt" "sort" + "github.com/operator-framework/api/pkg/operators" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-registry/alpha/property" ) @@ -99,8 +102,21 @@ func ObjectsAndPropertiesFromBundle(b *Bundle) ([]string, []property.Property, e if err != nil { return nil, nil, fmt.Errorf("marshal object %s/%s (%s) to json: %v", obj.GetName(), obj.GetNamespace(), obj.GroupVersionKind(), err) } - props = append(props, property.MustBuildBundleObjectData(objData)) objects = append(objects, string(objData)) + + // Make an olm.bundle.object property if there is no bundle image set. + // Otherwise, make a olm.csv.metadata property if the object is a CSV + // (and fallback to olm.bundle.object if parsing the CSV fails). + if b.BundleImage == "" { + props = append(props, property.MustBuildBundleObjectData(objData)) + } else if obj.GetKind() == operators.ClusterServiceVersionKind { + var csv v1alpha1.ClusterServiceVersion + if err := json.Unmarshal(objData, &csv); err != nil { + props = append(props, property.MustBuildBundleObjectData(objData)) + } else { + props = append(props, property.MustBuildCSVMetadata(csv)) + } + } } if packageProvidedProperty == nil { diff --git a/pkg/registry/registry_to_model_test.go b/pkg/registry/registry_to_model_test.go index 8d9f7dcb9..264507fd7 100644 --- a/pkg/registry/registry_to_model_test.go +++ b/pkg/registry/registry_to_model_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,7 +18,7 @@ func TestObjectsAndPropertiesFromBundle(t *testing.T) { actualObjs, actualProps, err := ObjectsAndPropertiesFromBundle(registryBundle) require.NoError(t, err) assert.ElementsMatch(t, testExpectedObjects(), actualObjs) - assert.ElementsMatch(t, testExpectedProperties(), actualProps) + assert.ElementsMatch(t, testExpectedProperties(t), actualProps) } const testBundleDir = "../../bundles/etcd.0.9.2" @@ -28,16 +29,23 @@ func testRegistryBundle(t *testing.T) *Bundle { return input.Bundle } +const expectedCSV = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}" + func testExpectedObjects() []string { return []string{ "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", - "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}", + expectedCSV, "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", } } -func testExpectedProperties() []property.Property { +func testExpectedProperties(t *testing.T) []property.Property { + t.Helper() + var csv v1alpha1.ClusterServiceVersion + if err := json.Unmarshal([]byte(expectedCSV), &csv); err != nil { + t.Fatalf("failed to unmarshal CSV: %v", err) + } props := []property.Property{ property.MustBuildPackage("etcd", "0.9.2"), property.MustBuildGVKRequired("etcd.database.coreos.com", "v1beta2", "EtcdCluster"), @@ -49,10 +57,7 @@ func testExpectedProperties() []property.Property { Type: "olm.constraint", Value: json.RawMessage(`{"cel":{"rule":"properties.exists(p, p.type == \"certified\")"},"failureMessage":"require to have \"certified\""}`), }, - } - - for _, obj := range testExpectedObjects() { - props = append(props, property.MustBuildBundleObjectData([]byte(obj))) + property.MustBuildCSVMetadata(csv), } return props } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index a3f001d12..433b85ff1 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -1,6 +1,7 @@ package server import ( + "fmt" "io" "net" "os" @@ -50,10 +51,13 @@ func createDBStore(dbPath string) *sqlite.SQLQuerier { if err := loader.Populate(); err != nil { logrus.Fatal(err) } + if _, err := db.Exec("UPDATE operatorbundle SET bundlepath = 'fake/etcd-operator:v0.9.2' WHERE name = 'etcdoperator.v0.9.2'"); err != nil { + logrus.Fatal(err) + } if err := db.Close(); err != nil { logrus.Fatal(err) } - store, err := sqlite.NewSQLLiteQuerier(dbPath) + store, err := sqlite.NewSQLLiteQuerier(dbPath, sqlite.OmitManifests(true)) if err != nil { logrus.Fatal(err) } @@ -217,8 +221,8 @@ func testGetPackage(addr string) func(*testing.T) { } func TestGetBundle(t *testing.T) { - t.Run("Sqlite", testGetBundle(dbAddress, etcdoperator_v0_9_2("alpha", false, false))) - t.Run("FBCJsonCache", testGetBundle(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true))) + t.Run("Sqlite", testGetBundle(dbAddress, etcdoperator_v0_9_2("alpha", false, false, includeManifestsAll))) + t.Run("FBCJsonCache", testGetBundle(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true, includeManifestsCSVOnly))) } func testGetBundle(addr string, expected *api.Bundle) func(*testing.T) { @@ -235,13 +239,13 @@ func testGetBundle(addr string, expected *api.Bundle) func(*testing.T) { func TestGetBundleForChannel(t *testing.T) { { - b := etcdoperator_v0_9_2("alpha", false, false) + b := etcdoperator_v0_9_2("alpha", false, false, includeManifestsAll) t.Run("Sqlite", testGetBundleForChannel(dbAddress, &api.Bundle{ CsvName: b.CsvName, CsvJson: b.CsvJson + "\n", })) } - t.Run("FBCJsonCache", testGetBundleForChannel(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true))) + t.Run("FBCJsonCache", testGetBundleForChannel(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true, includeManifestsCSVOnly))) } func testGetBundleForChannel(addr string, expected *api.Bundle) func(*testing.T) { @@ -332,8 +336,8 @@ func testGetChannelEntriesThatReplace(addr string) func(*testing.T) { } func TestGetBundleThatReplaces(t *testing.T) { - t.Run("Sqlite", testGetBundleThatReplaces(dbAddress, etcdoperator_v0_9_2("alpha", false, false))) - t.Run("FBCJsonCache", testGetBundleThatReplaces(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true))) + t.Run("Sqlite", testGetBundleThatReplaces(dbAddress, etcdoperator_v0_9_2("alpha", false, false, includeManifestsAll))) + t.Run("FBCJsonCache", testGetBundleThatReplaces(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true, includeManifestsCSVOnly))) } func testGetBundleThatReplaces(addr string, expected *api.Bundle) func(*testing.T) { @@ -348,8 +352,8 @@ func testGetBundleThatReplaces(addr string, expected *api.Bundle) func(*testing. } func TestGetBundleThatReplacesSynthetic(t *testing.T) { - t.Run("Sqlite", testGetBundleThatReplacesSynthetic(dbAddress, etcdoperator_v0_9_2("alpha", false, false))) - t.Run("FBCJsonCache", testGetBundleThatReplacesSynthetic(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true))) + t.Run("Sqlite", testGetBundleThatReplacesSynthetic(dbAddress, etcdoperator_v0_9_2("alpha", false, false, includeManifestsAll))) + t.Run("FBCJsonCache", testGetBundleThatReplacesSynthetic(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true, includeManifestsCSVOnly))) } func testGetBundleThatReplacesSynthetic(addr string, expected *api.Bundle) func(*testing.T) { @@ -558,8 +562,8 @@ func testGetLatestChannelEntriesThatProvide(addr string) func(t *testing.T) { } func TestGetDefaultBundleThatProvides(t *testing.T) { - t.Run("Sqlite", testGetDefaultBundleThatProvides(dbAddress, etcdoperator_v0_9_2("alpha", false, false))) - t.Run("FBCJsonCache", testGetDefaultBundleThatProvides(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true))) + t.Run("Sqlite", testGetDefaultBundleThatProvides(dbAddress, etcdoperator_v0_9_2("alpha", false, false, includeManifestsAll))) + t.Run("FBCJsonCache", testGetDefaultBundleThatProvides(jsonCacheAddress, etcdoperator_v0_9_2("alpha", false, true, includeManifestsCSVOnly))) } func testGetDefaultBundleThatProvides(addr string, expected *api.Bundle) func(*testing.T) { @@ -575,11 +579,11 @@ func testGetDefaultBundleThatProvides(addr string, expected *api.Bundle) func(*t func TestListBundles(t *testing.T) { t.Run("Sqlite", testListBundles(dbAddress, - etcdoperator_v0_9_2("alpha", true, false), - etcdoperator_v0_9_2("stable", true, false))) + etcdoperator_v0_9_2("alpha", true, false, includeManifestsNone), + etcdoperator_v0_9_2("stable", true, false, includeManifestsNone))) t.Run("FBCJsonCache", testListBundles(jsonCacheAddress, - etcdoperator_v0_9_2("alpha", true, true), - etcdoperator_v0_9_2("stable", true, true))) + etcdoperator_v0_9_2("alpha", true, true, includeManifestsNone), + etcdoperator_v0_9_2("stable", true, true, includeManifestsNone))) } func testListBundles(addr string, etcdAlpha *api.Bundle, etcdStable *api.Bundle) func(*testing.T) { @@ -695,19 +699,20 @@ func stripPlural(gvks []*api.GroupVersionKind) { } } -func etcdoperator_v0_9_2(channel string, addSkipsReplaces, addExtraProperties bool) *api.Bundle { +type includeManifests string + +const ( + includeManifestsAll includeManifests = "all" + includeManifestsNone includeManifests = "none" + includeManifestsCSVOnly includeManifests = "csvOnly" +) + +func etcdoperator_v0_9_2(channel string, addSkipsReplaces, addExtraProperties bool, includeManifests includeManifests) *api.Bundle { b := &api.Bundle{ CsvName: "etcdoperator.v0.9.2", PackageName: "etcd", ChannelName: channel, - CsvJson: "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"olm.properties\":\"[{\\\"type\\\":\\\"other\\\",\\\"value\\\":{\\\"its\\\":\\\"notdefined\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel1\\\"}}]\",\"olm.skipRange\":\"\\u003c 0.6.0\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"relatedImages\":[{\"image\":\"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84\",\"name\":\"etcd-v3.4.0\"},{\"image\":\"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f\",\"name\":\"etcd-3.4.1\"}],\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}", - Object: []string{ - "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", - "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", - "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"olm.properties\":\"[{\\\"type\\\":\\\"other\\\",\\\"value\\\":{\\\"its\\\":\\\"notdefined\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel1\\\"}}]\",\"olm.skipRange\":\"\\u003c 0.6.0\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"relatedImages\":[{\"image\":\"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84\",\"name\":\"etcd-v3.4.0\"},{\"image\":\"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f\",\"name\":\"etcd-3.4.1\"}],\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}", - "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", - }, - BundlePath: "", + BundlePath: "fake/etcd-operator:v0.9.2", Dependencies: []*api.Dependency{ { Type: "olm.gvk", @@ -764,5 +769,21 @@ func etcdoperator_v0_9_2(channel string, addSkipsReplaces, addExtraProperties bo {Type: "olm.gvk.required", Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`}, }...) } + switch includeManifests { + case includeManifestsAll: + b.CsvJson = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"olm.properties\":\"[{\\\"type\\\":\\\"other\\\",\\\"value\\\":{\\\"its\\\":\\\"notdefined\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel1\\\"}}]\",\"olm.skipRange\":\"\\u003c 0.6.0\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"relatedImages\":[{\"image\":\"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84\",\"name\":\"etcd-v3.4.0\"},{\"image\":\"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f\",\"name\":\"etcd-3.4.1\"}],\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}" + b.Object = []string{ + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", + b.CsvJson, + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", + } + case includeManifestsNone: + case includeManifestsCSVOnly: + b.CsvJson = "{\"kind\":\"ClusterServiceVersion\",\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"metadata\":{\"name\":\"etcdoperator.v0.9.2\",\"creationTimestamp\":null,\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"olm.properties\":\"[{\\\"type\\\":\\\"other\\\",\\\"value\\\":{\\\"its\\\":\\\"notdefined\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel\\\"}},{\\\"type\\\":\\\"olm.label\\\",\\\"value\\\":{\\\"label\\\":\\\"testlabel1\\\"}}]\",\"olm.skipRange\":\"\\u003c 0.6.0\",\"tectonic-visibility\":\"ocs\"}},\"spec\":{\"install\":{\"strategy\":\"deployment\",\"spec\":{\"deployments\":null}},\"version\":\"0.9.2\",\"maturity\":\"alpha\",\"customresourcedefinitions\":{\"owned\":[{\"name\":\"etcdclusters.etcd.database.coreos.com\",\"version\":\"v1beta2\",\"kind\":\"EtcdCluster\",\"displayName\":\"etcd Cluster\",\"description\":\"Represents a cluster of etcd nodes.\",\"resources\":[{\"name\":\"\",\"kind\":\"Service\",\"version\":\"v1\"},{\"name\":\"\",\"kind\":\"Pod\",\"version\":\"v1\"}],\"statusDescriptors\":[{\"path\":\"members\",\"displayName\":\"Member Status\",\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"path\":\"serviceName\",\"displayName\":\"Service\",\"description\":\"The service at which the running etcd cluster can be accessed.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"path\":\"size\",\"displayName\":\"Cluster Size\",\"description\":\"The current size of the etcd cluster.\"},{\"path\":\"currentVersion\",\"displayName\":\"Current Version\",\"description\":\"The current version of the etcd cluster.\"},{\"path\":\"targetVersion\",\"displayName\":\"Target Version\",\"description\":\"The target version of the etcd cluster, after upgrading.\"},{\"path\":\"phase\",\"displayName\":\"Status\",\"description\":\"The current status of the etcd cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"path\":\"reason\",\"displayName\":\"Status Details\",\"description\":\"Explanation for the current status of the cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"specDescriptors\":[{\"path\":\"size\",\"displayName\":\"Size\",\"description\":\"The desired number of member Pods for the etcd cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"path\":\"pod.resources\",\"displayName\":\"Resource Requirements\",\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}]},{\"name\":\"etcdbackups.etcd.database.coreos.com\",\"version\":\"v1beta2\",\"kind\":\"EtcdBackup\",\"displayName\":\"etcd Backup\",\"description\":\"Represents the intent to backup an etcd cluster.\",\"statusDescriptors\":[{\"path\":\"succeeded\",\"displayName\":\"Succeeded\",\"description\":\"Indicates if the backup was successful.\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"path\":\"reason\",\"displayName\":\"Reason\",\"description\":\"Indicates the reason for any backup related failures.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"specDescriptors\":[{\"path\":\"etcdEndpoints\",\"displayName\":\"etcd Endpoint(s)\",\"description\":\"Specifies the endpoints of an etcd cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"path\":\"s3.path\",\"displayName\":\"S3 Path\",\"description\":\"The full AWS S3 path where the backup is saved.\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"path\":\"s3.awsSecret\",\"displayName\":\"AWS Secret\",\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}]},{\"name\":\"etcdrestores.etcd.database.coreos.com\",\"version\":\"v1beta2\",\"kind\":\"EtcdRestore\",\"displayName\":\"etcd Restore\",\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"statusDescriptors\":[{\"path\":\"succeeded\",\"displayName\":\"Succeeded\",\"description\":\"Indicates if the restore was successful.\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"path\":\"reason\",\"displayName\":\"Reason\",\"description\":\"Indicates the reason for any restore related failures.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"specDescriptors\":[{\"path\":\"etcdCluster.name\",\"displayName\":\"etcd Cluster\",\"description\":\"References the EtcdCluster which should be restored,\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"path\":\"s3.path\",\"displayName\":\"S3 Path\",\"description\":\"The full AWS S3 path where the backup is saved.\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"path\":\"s3.awsSecret\",\"displayName\":\"AWS Secret\",\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}]}],\"required\":[{\"name\":\"etcdclusters.etcd.database.coreos.com\",\"version\":\"v1beta2\",\"kind\":\"EtcdCluster\",\"displayName\":\"etcd Cluster\",\"description\":\"Represents a cluster of etcd nodes.\",\"resources\":[{\"name\":\"\",\"kind\":\"Service\",\"version\":\"v1\"},{\"name\":\"\",\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"path\":\"size\",\"displayName\":\"Size\",\"description\":\"The desired number of member Pods for the etcd cluster.\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}]}]},\"apiservicedefinitions\":{},\"displayName\":\"etcd\",\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"maintainers\":[{\"name\":\"CoreOS, Inc\",\"email\":\"support@coreos.com\"}],\"provider\":{\"name\":\"CoreOS, Inc\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"cleanup\":{\"enabled\":false},\"relatedImages\":[{\"name\":\"\",\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\"},{\"name\":\"etcd-v3.4.0\",\"image\":\"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84\"},{\"name\":\"etcd-3.4.1\",\"image\":\"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f\"}]},\"status\":{\"cleanup\":{}}}" + b.Object = []string{b.CsvJson} + default: + panic(fmt.Sprintf("unexpected includeManifests value: %q", includeManifests)) + } return b }