diff --git a/AGENTS.md b/AGENTS.md index cf2f88c9a..87b46a073 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,18 +9,15 @@ The Operator Registry is a Kubernetes/OpenShift component that provides operator ## Key Components ### Binaries -- **`opm`**: Main CLI tool for generating and updating registry databases and index images -- **`initializer`**: **(Deprecated)** Converts operator manifests to SQLite database format -- **`registry-server`**: **(Deprecated)** Exposes gRPC interface to SQLite databases -- **`configmap-server`**: Parses ConfigMaps into SQLite databases and exposes gRPC interface +- **`opm`**: Main CLI tool for rendering and serving file-based catalogs ### Libraries - **`pkg/client`**: High-level client interface for gRPC API - **`pkg/api`**: Low-level client libraries for gRPC interface - **`pkg/registry`**: Core registry types (Packages, Channels, Bundles) -- **`pkg/sqlite`**: **(Deprecated)** SQLite database interfaces for manifests - **`pkg/lib`**: External interfaces and standards for operator bundles - **`pkg/containertools`**: Container tooling integration +- **`pkg/cache`**: Caching backends for file-based catalogs ### Alpha Features - **`alpha/declcfg`**: Declarative configuration format @@ -33,7 +30,7 @@ The Operator Registry is a Kubernetes/OpenShift component that provides operator - **Go 1.24.4**: Minimum Go version required - **Cobra CLI**: Command-line interface framework - **gRPC**: Primary API communication protocol -- **SQLite**: Database backend for registry data +- **Declarative Config (FBC)**: File-based catalog format for registry data - **OCI Images**: Container image format for bundles ### Testing @@ -93,11 +90,11 @@ podman build -t quay.io/my-namespace/my-bundle:latest -f bundle.Dockerfile . # IMPORTANT: Bundle images must be published to a registry before they can be consumed podman push quay.io/my-namespace/my-bundle:latest -# Add bundle to index (deprecated - use file-based catalogs instead) -opm index add --bundles quay.io/my-namespace/my-bundle:latest --from-index quay.io/my-namespace/my-index:latest --tag quay.io/my-namespace/my-index:latest +# Render bundle to declarative config +opm render quay.io/my-namespace/my-bundle:latest --output yaml > catalog.yaml -# Generate index image (deprecated - use file-based catalogs instead) -opm index add --bundles quay.io/my-namespace/my-bundle:latest --tag quay.io/my-namespace/my-index:latest +# Generate catalog from multiple bundles +opm render quay.io/my-namespace/my-bundle:v1.0.0 quay.io/my-namespace/my-bundle:v1.1.0 --output yaml ``` **⚠️ Critical Requirements:** @@ -117,19 +114,7 @@ model, err := declcfg.ConvertToModel(cfg) err = declcfg.Write(cfg, "output.yaml") ``` -### 4. Database Operations **(Deprecated)** -```go -// Create SQLite database -db, err := sqlite.Open("registry.db") - -// Add bundle to database -err = db.AddBundle(bundle) - -// Query packages -packages, err := db.ListPackages() -``` - -### 5. Serving Catalog Content with `opm serve` +### 4. Serving Catalog Content with `opm serve` The `opm serve` command exposes operator catalog data via a gRPC interface for consumption by OLM. ```bash @@ -214,121 +199,18 @@ cache, err := cache.New("/cache/dir", - Cache directory should be persistent for production deployments - Use `--cache-only` for pre-building cache in CI/CD pipelines -## Deprecated SQLite Commands and Operations - -⚠️ **IMPORTANT DEPRECATION NOTICE**: SQLite-based catalogs and their related subcommands are deprecated. Support for them will be removed in a future release. Please migrate your catalog workflows to the new file-based catalog format. - -### Deprecated Commands - -#### `opm registry` Commands (All Deprecated) -- **`opm registry serve`** - **(Deprecated)** Serve SQLite database via gRPC -- **`opm registry add`** - **(Deprecated)** Add bundles to SQLite database -- **`opm registry rm`** - **(Deprecated)** Remove packages from SQLite database -- **`opm registry prune`** - **(Deprecated)** Prune SQLite database -- **`opm registry prune-stranded`** - **(Deprecated)** Prune stranded bundles from SQLite database -- **`opm registry deprecatetruncate`** - **(Deprecated)** Deprecate bundles in SQLite database -- **`opm registry mirror`** - **(Deprecated)** Mirror SQLite-based catalogs - -#### `opm index` Commands (All Deprecated) -- **`opm index add`** - **(Deprecated)** Add bundles to SQLite-based index -- **`opm index rm`** - **(Deprecated)** Delete operators from SQLite-based index -- **`opm index export`** - **(Deprecated)** Export SQLite-based index -- **`opm index prune`** - **(Deprecated)** Prune SQLite-based index -- **`opm index prune-stranded`** - **(Deprecated)** Prune stranded bundles from SQLite-based index -- **`opm index deprecatetruncate`** - **(Deprecated)** Deprecate bundles in SQLite-based index - -### Deprecated Libraries and Interfaces - -#### `pkg/sqlite` Package (Deprecated) -- **`SQLQuerier`** - **(Deprecated)** Query interface for SQLite databases -- **`SQLLoader`** - **(Deprecated)** Load bundles into SQLite databases -- **`DeprecationAwareLoader`** - **(Deprecated)** Handle bundle deprecations in SQLite -- **`SQLDeprecator`** - **(Deprecated)** Deprecate bundles in SQLite databases - -#### `pkg/lib/registry` Package (Deprecated) -- **`RegistryUpdater`** - **(Deprecated)** Update SQLite-based registries -- **`RegistryDeleter`** - **(Deprecated)** Delete from SQLite-based registries -- **`RegistryDeprecator`** - **(Deprecated)** Deprecate bundles in SQLite-based registries - -### Migration Path - -**From SQLite to File-Based Catalogs:** - -1. **Replace `opm registry serve`** → **Use `opm serve`** - ```bash - # Old (deprecated) - opm registry serve --database bundles.db - - # New (recommended) - opm serve ./catalog-directory - ``` - -2. **Replace `opm index add`** → **Use `opm alpha generate dockerfile` + `opm serve`** - ```bash - # Old (deprecated) - opm index add --bundles quay.io/my/bundle:latest --tag quay.io/my/index:latest - - # New (recommended) - opm alpha generate dockerfile ./catalog-directory - # Build and serve the generated Dockerfile - ``` - -3. **Replace SQLite database operations** → **Use declarative config files** - ```bash - # Old (deprecated) - SQLite database manipulation - opm registry add --database bundles.db --bundles quay.io/my/bundle:latest - - # New (recommended) - File-based catalog - # Create catalog files directly in ./catalog-directory/ - ``` - -4. **Convert existing SQLite catalogs** → **Use migration tools** - ```bash - # Convert SQLite index image to FBC - opm migrate quay.io/my-namespace/my-index:latest ./fbc-output --output yaml - - # Convert SQLite database to FBC - opm render ./bundles.db --output yaml > catalog.yaml - - # Convert FBC to a basic catalog template for simpler maintenance - opm alpha render-template basic ./catalog.yaml - ``` - -### Deprecation Warnings - -When using deprecated commands, you will see warnings like: -``` -DEPRECATION NOTICE: -Sqlite-based catalogs and their related subcommands are deprecated. Support for -them will be removed in a future release. Please migrate your catalog workflows -to the new file-based catalog format. -``` - -**Action Required**: Plan your migration to file-based catalogs to avoid future compatibility issues. - -### Migration from SQLite to File-Based Catalogs (FBC) +## Removed SQLite Support -#### Using `opm migrate` -The `opm migrate` command converts SQLite-based index images or database files to file-based catalogs: +⚠️ **NOTICE**: SQLite-based catalogs and their related commands (`opm registry`, `opm index`) have been removed from this project. All catalog workflows now use the file-based catalog (FBC) format exclusively. -```bash -# Migrate from SQLite index image to FBC -opm migrate quay.io/my-namespace/my-index:latest ./fbc-output --output yaml - -# Migrate from SQLite database file to FBC -opm migrate ./bundles.db ./fbc-output --output json +**Migration**: For older versions that supported SQLite-to-FBC migration, refer to previous releases or documentation versions. -# Migrate with specific migration level -opm migrate quay.io/my-namespace/my-index:latest ./fbc-output -``` +### Creating File-Based Catalogs #### Using `opm render` -The `opm render` command can convert various sources to FBC format: +The `opm render` command converts various sources to FBC format: ```bash -# Render from SQLite database to FBC -opm render ./bundles.db --output yaml > catalog.yaml - # Render from bundle images to FBC opm render quay.io/my/bundle:v1.0.0 quay.io/my/bundle:v1.1.0 --output json @@ -350,30 +232,6 @@ opm alpha render-template semver ./semver-template.yaml --output json opm alpha render-template ./template-file.yaml --output yaml ``` -#### Combined Migration Workflow -For complex migrations, combine multiple tools: - -```bash -# Step 1: Migrate SQLite to FBC -opm migrate quay.io/my-namespace/my-index:latest ./fbc-output --output yaml - -# Step 2: Convert to basic template format (if needed) -opm alpha render-template basic ./fbc-output/package.yaml --output yaml > template.yaml - -# Step 3: Modify template as needed, then render final FBC -opm alpha render-template basic ./template.yaml --output yaml > final-catalog.yaml - -# Step 4: Serve the new FBC catalog -opm serve ./final-catalog.yaml -``` - -#### Migration Considerations -- **Bundle images must exist** in registries before they can be referenced in FBC -- **Image references are required** - use `--alpha-image-ref-template` if bundle images don't exist yet -- **Migration levels** control which schema transformations are applied -- **Output formats** include JSON (streamable) and YAML (human-readable) -- **Templates provide flexibility** for custom catalog generation workflows - ## File Organization ### Key Directories @@ -448,23 +306,17 @@ func TestMyFunction(t *testing.T) { ### Common Issues 1. **Bundle validation errors**: Check bundle format and annotations 2. **Image pull failures**: Verify image references and registry access -3. **Database corruption**: Rebuild database from source bundles -4. **Template rendering errors**: Check template syntax and schema -5. **Cache integrity failures**: Rebuild cache or disable integrity checks -6. **gRPC connection issues**: Verify port availability and firewall settings -7. **Memory issues with large catalogs**: Use persistent cache directory -8. **Slow startup times**: Pre-build cache with `--cache-only` flag -9. **SQLite deprecation warnings**: Migrate to file-based catalogs using `opm serve` -10. **Hidden commands**: Some deprecated commands are hidden but still accessible +3. **Template rendering errors**: Check template syntax and schema +4. **Cache integrity failures**: Rebuild cache or disable integrity checks +5. **gRPC connection issues**: Verify port availability and firewall settings +6. **Memory issues with large catalogs**: Use persistent cache directory +7. **Slow startup times**: Pre-build cache with `--cache-only` flag ### Debug Commands ```bash # Validate bundle opm alpha bundle validate ./bundle -# Inspect index -opm index export --from-index quay.io/my-namespace/my-index:latest - # Debug template rendering opm alpha render-template --help @@ -477,10 +329,6 @@ opm serve ./catalog --pprof-addr localhost:6060 --pprof-capture-profiles # Test gRPC connectivity grpcurl -plaintext localhost:50051 list grpcurl -plaintext localhost:50051 api.Registry/ListPackages - -# Deprecated SQLite commands (avoid using these) -opm registry serve --database bundles.db # Use 'opm serve' instead -opm index add --bundles quay.io/my/bundle:latest --tag quay.io/my/index:latest # Use file-based catalogs ``` ## Contributing Guidelines diff --git a/Makefile b/Makefile index 432f7703b..c9bde2e04 100644 --- a/Makefile +++ b/Makefile @@ -27,8 +27,8 @@ $(PROTOC): null := space := $(null) # comma := , -# default to json1 for sqlite3 and containers_image_openpgp for containers/image -TAGS := json1,containers_image_openpgp +# default to containers_image_openpgp for containers/image +TAGS := containers_image_openpgp # Cluster to use for e2e testing CLUSTER ?= "" @@ -62,8 +62,8 @@ build: clean $(CMDS) $(OPM) cross: opm_version_flags=-ldflags "-X '$(PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'" cross: ifeq ($(shell go env GOARCH),amd64) - GOOS=darwin CC=o64-clang CXX=o64-clang++ CGO_ENABLED=1 CGO_LDFLAGS='-Wl,-undefined,dynamic_lookup' $(GO) build $(opm_version_flags) -tags=$(TAGS) -o "bin/darwin-amd64-opm" --ldflags "-extld=o64-clang -extldflags=-Wl,-undefined,dynamic_lookup" ./cmd/opm - GOOS=windows CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1 $(GO) build $(opm_version_flags) -tags=$(TAGS) -o "bin/windows-amd64-opm" --ldflags "-extld=x86_64-w64-mingw32-gcc" -buildmode=exe ./cmd/opm + GOOS=darwin CGO_ENABLED=0 $(GO) build $(opm_version_flags) -tags=$(TAGS) -o "bin/darwin-amd64-opm" ./cmd/opm + GOOS=windows CGO_ENABLED=0 $(GO) build $(opm_version_flags) -tags=$(TAGS) -o "bin/windows-amd64-opm" -buildmode=exe ./cmd/opm endif .PHONY: static diff --git a/README.md b/README.md index 8e1ea563c..d0a72809c 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,12 @@ contribution. See the [DCO](DCO) file for details. This project provides the following binaries: * `opm`, which generates and updates registry databases as well as the index images that encapsulate them. - * `initializer`, which takes as an input a directory of operator manifests and outputs a sqlite database containing the same data for querying - * Deprecated - use `opm registry|index add` instead - * `registry-server`, which takes a sqlite database loaded with manifests, and exposes a gRPC interface to it. - * Deprecated - use `opm registry serve` instead - * `configmap-server`, which takes a kubeconfig and a configmap reference, and parses the configmap into the sqlite database before exposing it via the same interface as `registry-server`. And libraries: * `pkg/client` - providing a high-level client interface for the gRPC api. -* `pkg/api` - providing low-level client libraries for the gRPC interface exposed by `registry-server`. +* `pkg/api` - providing low-level client libraries for the gRPC interface. * `pkg/registry` - providing basic registry types like Packages, Channels, and Bundles. -* `pkg/sqlite` - providing interfaces for building sqlite manifest databases from `ConfigMap`s or directories, and for querying an existing sqlite database. * `pkg/lib` - providing external interfaces for interacting with this project as an api that defines a set of standards for operator bundles and indexes. * `pkg/containertools` - providing an interface to interact with and shell out to common container tooling binaries (if installed on the environment) diff --git a/alpha/action/init.go b/alpha/action/init.go index 242d8f818..d8ad617d1 100644 --- a/alpha/action/init.go +++ b/alpha/action/init.go @@ -5,6 +5,7 @@ import ( "io" "github.com/h2non/filetype" + "github.com/h2non/go-is-svg" "github.com/operator-framework/operator-registry/alpha/declcfg" ) @@ -32,21 +33,44 @@ func (i Init) Run() (*declcfg.Package, error) { } if i.IconReader != nil { - iconData, err := io.ReadAll(i.IconReader) + icon, err := processIcon(i.IconReader) if err != nil { - return nil, fmt.Errorf("read icon: %v", err) + return nil, err } - iconType, err := filetype.Match(iconData) - if err != nil { - return nil, fmt.Errorf("detect icon mediatype: %v", err) + pkg.Icon = icon + } + return pkg, nil +} + +func processIcon(iconReader io.Reader) (*declcfg.Icon, error) { + iconData, err := io.ReadAll(iconReader) + if err != nil { + return nil, fmt.Errorf("read icon: %v", err) + } + + // Try filetype detection first + iconType, err := filetype.Match(iconData) + if err != nil { + return nil, fmt.Errorf("detect icon mediatype: %v", err) + } + + var mediaType string + // If filetype didn't detect it, check if it's SVG + if iconType.MIME.Value == "" { + if issvg.Is(iconData) { + mediaType = "image/svg+xml" + } else { + return nil, fmt.Errorf("detected invalid type %q: not an image", iconType.MIME.Value) } + } else { if iconType.MIME.Type != "image" { return nil, fmt.Errorf("detected invalid type %q: not an image", iconType.MIME.Value) } - pkg.Icon = &declcfg.Icon{ - Data: iconData, - MediaType: iconType.MIME.Value, - } + mediaType = iconType.MIME.Value } - return pkg, nil + + return &declcfg.Icon{ + Data: iconData, + MediaType: mediaType, + }, nil } diff --git a/alpha/action/list.go b/alpha/action/list.go index 80c014d1d..1f4d2ce94 100644 --- a/alpha/action/list.go +++ b/alpha/action/list.go @@ -13,7 +13,6 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/pkg/image" ) @@ -23,23 +22,22 @@ type ListPackages struct { } func (l *ListPackages) Run(ctx context.Context) (*ListPackagesResult, error) { - m, err := indexRefToModel(ctx, l.IndexReference, l.Registry) + cfg, err := indexRefToDeclcfg(ctx, l.IndexReference, l.Registry) if err != nil { return nil, err } - pkgs := []model.Package{} - for _, pkg := range m { - pkgs = append(pkgs, *pkg) - } + pkgs := cfg.Packages sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name }) - return &ListPackagesResult{Packages: pkgs}, nil + return &ListPackagesResult{Packages: pkgs, Channels: cfg.Channels, Bundles: cfg.Bundles}, nil } type ListPackagesResult struct { - Packages []model.Package + Packages []declcfg.Package + Channels []declcfg.Channel + Bundles []declcfg.Bundle } func (r *ListPackagesResult) WriteColumns(w io.Writer) error { @@ -48,29 +46,91 @@ func (r *ListPackagesResult) WriteColumns(w io.Writer) error { return err } for _, pkg := range r.Packages { - if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", pkg.Name, getDisplayName(pkg), pkg.DefaultChannel.Name); err != nil { + displayName := getDisplayName(pkg, r.Channels, r.Bundles) + if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", pkg.Name, displayName, pkg.DefaultChannel); err != nil { return err } } return tw.Flush() } -func getDisplayName(pkg model.Package) string { - if pkg.DefaultChannel == nil { +func getDisplayName(pkg declcfg.Package, channels []declcfg.Channel, bundles []declcfg.Bundle) string { + if pkg.DefaultChannel == "" { + return "" + } + + // Find the default channel + var defaultCh *declcfg.Channel + for i, ch := range channels { + if ch.Package == pkg.Name && ch.Name == pkg.DefaultChannel { + defaultCh = &channels[i] + break + } + } + if defaultCh == nil { + return "" + } + + // Find the head bundle + head, err := findChannelHead(defaultCh.Entries) + if err != nil { return "" } - head, err := pkg.DefaultChannel.Head() - if err != nil || head == nil || head.CsvJSON == "" { + + // Find the bundle + var headBundle *declcfg.Bundle + for i, b := range bundles { + if b.Package == pkg.Name && b.Name == head { + headBundle = &bundles[i] + break + } + } + if headBundle == nil || headBundle.CsvJSON == "" { return "" } csv := v1alpha1.ClusterServiceVersion{} - if err := json.Unmarshal([]byte(head.CsvJSON), &csv); err != nil { + if err := json.Unmarshal([]byte(headBundle.CsvJSON), &csv); err != nil { return "" } return csv.Spec.DisplayName } +// findChannelHead finds the head bundle of a channel by analyzing the replaces chain. +func findChannelHead(entries []declcfg.ChannelEntry) (string, error) { + if len(entries) == 0 { + return "", fmt.Errorf("channel has no entries") + } + + // Build a map of bundles that are replaced + replaced := make(map[string]bool) + for _, entry := range entries { + if entry.Replaces != "" { + replaced[entry.Replaces] = true + } + for _, skip := range entry.Skips { + replaced[skip] = true + } + } + + // Find bundles that are not replaced by anything + var heads []string + for _, entry := range entries { + if !replaced[entry.Name] { + heads = append(heads, entry.Name) + } + } + + if len(heads) == 0 { + return "", fmt.Errorf("channel has circular replaces chain, no head found") + } + if len(heads) > 1 { + return "", fmt.Errorf("channel has multiple heads: %v", heads) + } + + return heads[0], nil +} + type ListChannels struct { IndexReference string PackageName string @@ -78,26 +138,19 @@ type ListChannels struct { } func (l *ListChannels) Run(ctx context.Context) (*ListChannelsResult, error) { - m, err := indexRefToModel(ctx, l.IndexReference, l.Registry) - if err != nil { - return nil, err - } - - pkgs, err := getPackages(m, l.PackageName) + cfg, err := indexRefToDeclcfg(ctx, l.IndexReference, l.Registry) if err != nil { return nil, err } - channels := []model.Channel{} - for _, pkg := range pkgs { - for _, ch := range pkg.Channels { - channels = append(channels, *ch) - } + channels := filterChannelsByPackage(cfg.Channels, l.PackageName) + if l.PackageName != "" && len(channels) == 0 { + return nil, fmt.Errorf("package %q not found", l.PackageName) } sort.Slice(channels, func(i, j int) bool { - if channels[i].Package.Name != channels[j].Package.Name { - return channels[i].Package.Name < channels[j].Package.Name + if channels[i].Package != channels[j].Package { + return channels[i].Package < channels[j].Package } return channels[i].Name < channels[j].Name }) @@ -105,7 +158,7 @@ func (l *ListChannels) Run(ctx context.Context) (*ListChannelsResult, error) { } type ListChannelsResult struct { - Channels []model.Channel + Channels []declcfg.Channel } func (r *ListChannelsResult) WriteColumns(w io.Writer) error { @@ -115,13 +168,13 @@ func (r *ListChannelsResult) WriteColumns(w io.Writer) error { } for _, ch := range r.Channels { headStr := "" - head, err := ch.Head() + head, err := findChannelHead(ch.Entries) if err != nil { headStr = fmt.Sprintf("ERROR: %s", err) } else { - headStr = head.Name + headStr = head } - if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", ch.Package.Name, ch.Name, headStr); err != nil { + if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", ch.Package, ch.Name, headStr); err != nil { return err } } @@ -135,39 +188,24 @@ type ListBundles struct { } func (l *ListBundles) Run(ctx context.Context) (*ListBundlesResult, error) { - m, err := indexRefToModel(ctx, l.IndexReference, l.Registry) + cfg, err := indexRefToDeclcfg(ctx, l.IndexReference, l.Registry) if err != nil { return nil, err } - pkgs, err := getPackages(m, l.PackageName) - if err != nil { - return nil, err - } + bundles := filterBundlesByPackage(cfg.Bundles, cfg.Channels, l.PackageName) + channels := filterChannelsByPackage(cfg.Channels, l.PackageName) - bundles := []model.Bundle{} - for _, pkg := range pkgs { - for _, ch := range pkg.Channels { - for _, b := range ch.Bundles { - bundles = append(bundles, *b) - } - } + if l.PackageName != "" && len(bundles) == 0 { + return nil, fmt.Errorf("package %q not found", l.PackageName) } - sort.Slice(bundles, func(i, j int) bool { - if bundles[i].Package.Name != bundles[j].Package.Name { - return bundles[i].Package.Name < bundles[j].Package.Name - } - if bundles[i].Channel.Name != bundles[j].Channel.Name { - return bundles[i].Channel.Name < bundles[j].Channel.Name - } - return bundles[i].Name < bundles[j].Name - }) - return &ListBundlesResult{Bundles: bundles}, nil + return &ListBundlesResult{Bundles: bundles, Channels: channels}, nil } type ListBundlesResult struct { - Bundles []model.Bundle + Bundles []declcfg.Bundle + Channels []declcfg.Channel } func (r *ListBundlesResult) WriteColumns(w io.Writer) error { @@ -175,18 +213,81 @@ func (r *ListBundlesResult) WriteColumns(w io.Writer) error { if _, err := fmt.Fprintln(tw, "PACKAGE\tCHANNEL\tBUNDLE\tREPLACES\tSKIPS\tSKIP RANGE\tIMAGE"); err != nil { return err } + + // Build a map of bundle -> channels containing it + type bundleChannelEntry struct { + channel string + replaces string + skips []string + skipRange string + } + bundleToChannels := make(map[string][]bundleChannelEntry) + for _, ch := range r.Channels { + for _, entry := range ch.Entries { + key := ch.Package + "/" + entry.Name + bundleToChannels[key] = append(bundleToChannels[key], bundleChannelEntry{ + channel: ch.Name, + replaces: entry.Replaces, + skips: entry.Skips, + skipRange: entry.SkipRange, + }) + } + } + + // Build list of bundle instances (one per channel) + type bundleInstance struct { + pkg string + channel string + name string + replaces string + skips []string + skipRange string + image string + } + var instances []bundleInstance + for _, b := range r.Bundles { - if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", b.Package.Name, b.Channel.Name, b.Name, b.Replaces, strings.Join(b.Skips, ","), b.SkipRange, b.Image); err != nil { + key := b.Package + "/" + b.Name + channels := bundleToChannels[key] + + // Create one entry per channel + for _, ch := range channels { + instances = append(instances, bundleInstance{ + pkg: b.Package, + channel: ch.channel, + name: b.Name, + replaces: ch.replaces, + skips: ch.skips, + skipRange: ch.skipRange, + image: b.Image, + }) + } + } + + // Sort instances + sort.Slice(instances, func(i, j int) bool { + if instances[i].pkg != instances[j].pkg { + return instances[i].pkg < instances[j].pkg + } + if instances[i].channel != instances[j].channel { + return instances[i].channel < instances[j].channel + } + return instances[i].name < instances[j].name + }) + + // Write instances + for _, inst := range instances { + if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", inst.pkg, inst.channel, inst.name, inst.replaces, strings.Join(inst.skips, ","), inst.skipRange, inst.image); err != nil { return err } } return tw.Flush() } -func indexRefToModel(ctx context.Context, ref string, reg image.Registry) (model.Model, error) { +func indexRefToDeclcfg(ctx context.Context, ref string, reg image.Registry) (*declcfg.DeclarativeConfig, error) { render := Render{ Refs: []string{ref}, - AllowedRefMask: RefDCImage | RefDCDir | RefSqliteImage | RefSqliteFile, + AllowedRefMask: RefDCImage | RefDCDir, Registry: reg, } cfg, err := render.Run(ctx) @@ -197,16 +298,33 @@ func indexRefToModel(ctx context.Context, ref string, reg image.Registry) (model return nil, err } - return declcfg.ConvertToModel(*cfg) + return cfg, nil +} + +func filterChannelsByPackage(channels []declcfg.Channel, packageName string) []declcfg.Channel { + if packageName == "" { + return channels + } + + var result []declcfg.Channel + for _, ch := range channels { + if ch.Package == packageName { + result = append(result, ch) + } + } + return result } -func getPackages(m model.Model, packageName string) (model.Model, error) { +func filterBundlesByPackage(bundles []declcfg.Bundle, _ []declcfg.Channel, packageName string) []declcfg.Bundle { if packageName == "" { - return m, nil + return bundles } - pkg, ok := m[packageName] - if !ok { - return nil, fmt.Errorf("package %q not found", packageName) + + var result []declcfg.Bundle + for _, b := range bundles { + if b.Package == packageName { + result = append(result, b) + } } - return model.Model{packageName: pkg}, nil + return result } diff --git a/alpha/action/migrate.go b/alpha/action/migrate.go index 8122d1648..914c861dd 100644 --- a/alpha/action/migrate.go +++ b/alpha/action/migrate.go @@ -34,7 +34,7 @@ func (m Migrate) Run(ctx context.Context) error { Migrations: m.Migrations, // Only allow catalogs to be migrated. - AllowedRefMask: RefSqliteImage | RefSqliteFile | RefDCImage | RefDCDir, + AllowedRefMask: RefDCImage | RefDCDir, } if m.Registry != nil { r.Registry = m.Registry diff --git a/alpha/action/migrate_test.go b/alpha/action/migrate_test.go index a0b5d2771..4f1637779 100644 --- a/alpha/action/migrate_test.go +++ b/alpha/action/migrate_test.go @@ -4,14 +4,12 @@ import ( "context" "io/fs" "os" - "path/filepath" "testing" "github.com/stretchr/testify/require" "github.com/operator-framework/operator-registry/alpha/action" "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/pkg/containertools" "github.com/operator-framework/operator-registry/pkg/image" "github.com/operator-framework/operator-registry/pkg/lib/bundle" ) @@ -24,48 +22,10 @@ func TestMigrate(t *testing.T) { expectErr error } - sqliteBundles := map[image.Reference]string{ - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): "testdata/foo-bundle-v0.1.0", - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.2.0"): "testdata/foo-bundle-v0.2.0", - image.SimpleReference("test.registry/bar-operator/bar-bundle:v0.1.0"): "testdata/bar-bundle-v0.1.0", - image.SimpleReference("test.registry/bar-operator/bar-bundle:v0.2.0"): "testdata/bar-bundle-v0.2.0", - } - - sqliteDBDir := t.TempDir() - dbFile := filepath.Join(sqliteDBDir, "index.db") - err := generateSqliteFile(dbFile, sqliteBundles) - require.NoError(t, err) - - reg, err := newMigrateRegistry(t, sqliteBundles) + reg, err := newMigrateRegistry(t) require.NoError(t, err) specs := []spec{ - { - name: "SqliteImage/Success", - migrate: action.Migrate{ - CatalogRef: "test.registry/migrate/catalog:sqlite", - WriteFunc: declcfg.WriteYAML, - FileExt: ".yaml", - Registry: reg, - }, - expectedFiles: map[string]string{ - "foo/catalog.yaml": migrateFooCatalogSqlite(), - "bar/catalog.yaml": migrateBarCatalogSqlite(), - }, - }, - { - name: "SqliteFile/Success", - migrate: action.Migrate{ - CatalogRef: dbFile, - WriteFunc: declcfg.WriteYAML, - FileExt: ".yaml", - Registry: reg, - }, - expectedFiles: map[string]string{ - "foo/catalog.yaml": migrateFooCatalogSqlite(), - "bar/catalog.yaml": migrateBarCatalogSqlite(), - }, - }, { name: "DeclcfgImage/Success", migrate: action.Migrate{ @@ -100,20 +60,6 @@ func TestMigrate(t *testing.T) { }, expectErr: action.ErrNotAllowed, }, - { - name: "SqliteImage/Success/NoMigrations", - migrate: action.Migrate{ - CatalogRef: "test.registry/migrate/catalog:sqlite", - WriteFunc: declcfg.WriteYAML, - FileExt: ".yaml", - Registry: reg, - Migrations: nil, - }, - expectedFiles: map[string]string{ - "foo/catalog.yaml": migrateFooCatalogSqlite(), - "bar/catalog.yaml": migrateBarCatalogSqlite(), - }, - }, } for _, s := range specs { t.Run(s.name, func(t *testing.T) { @@ -141,12 +87,7 @@ func TestMigrate(t *testing.T) { } } -func newMigrateRegistry(t *testing.T, imageMap map[image.Reference]string) (image.Registry, error) { - subSqliteImage, err := generateSqliteFS(t, imageMap) - if err != nil { - return nil, err - } - +func newMigrateRegistry(_ *testing.T) (image.Registry, error) { subDeclcfgImage, err := fs.Sub(declcfgImage, "testdata/foo-index-v0.2.0-declcfg") if err != nil { return nil, err @@ -158,12 +99,6 @@ func newMigrateRegistry(t *testing.T, imageMap map[image.Reference]string) (imag } reg := &image.MockRegistry{RemoteImages: map[image.Reference]*image.MockImage{ - image.SimpleReference("test.registry/migrate/catalog:sqlite"): { - Labels: map[string]string{ - containertools.DbLocationLabel: "/database/index.db", - }, - FS: subSqliteImage, - }, image.SimpleReference("test.registry/foo-operator/foo-index-declcfg:v0.2.0"): { Labels: map[string]string{ "operators.operatorframework.io.index.configs.v1": "/foo", @@ -181,188 +116,6 @@ func newMigrateRegistry(t *testing.T, imageMap map[image.Reference]string) (imag return reg, nil } -func migrateFooCatalogSqlite() string { - return `--- -defaultChannel: beta -name: foo -schema: olm.package ---- -entries: -- name: foo.v0.1.0 - skipRange: <0.1.0 -- name: foo.v0.2.0 - replaces: foo.v0.1.0 - skipRange: <0.2.0 - skips: - - foo.v0.1.1 - - foo.v0.1.2 -name: beta -package: foo -schema: olm.channel ---- -entries: -- name: foo.v0.1.0 - skipRange: <0.1.0 -- name: foo.v0.2.0 - replaces: foo.v0.1.0 - skipRange: <0.2.0 - skips: - - foo.v0.1.1 - - foo.v0.1.2 -name: stable -package: foo -schema: olm.channel ---- -image: test.registry/foo-operator/foo-bundle:v0.1.0 -name: foo.v0.1.0 -package: foo -properties: -- type: olm.gvk - value: - group: test.foo - kind: Foo - version: v1 -- type: olm.gvk.required - value: - group: test.bar - kind: Bar - version: v1alpha1 -- type: olm.package - value: - packageName: foo - version: 0.1.0 -- type: olm.package.required - value: - packageName: bar - versionRange: <0.1.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMS4wIn0sIm5hbWUiOiJmb28udjAuMS4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= -relatedImages: -- image: test.registry/foo-operator/foo-bundle:v0.1.0 - name: "" -- image: test.registry/foo-operator/foo:v0.1.0 - name: operator -schema: olm.bundle ---- -image: test.registry/foo-operator/foo-bundle:v0.2.0 -name: foo.v0.2.0 -package: foo -properties: -- type: olm.gvk - value: - group: test.foo - kind: Foo - version: v1 -- type: olm.gvk.required - value: - group: test.bar - kind: Bar - version: v1alpha1 -- type: olm.package - value: - packageName: foo - version: 0.2.0 -- type: olm.package.required - value: - packageName: bar - versionRange: <0.1.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJmb28udjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwiaW5zdGFsbCI6eyJzcGVjIjp7ImRlcGxveW1lbnRzIjpbeyJuYW1lIjoiZm9vLW9wZXJhdG9yIiwic3BlYyI6eyJ0ZW1wbGF0ZSI6eyJzcGVjIjp7ImNvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQ6djAuMi4wIn1dfX19fSx7Im5hbWUiOiJmb28tb3BlcmF0b3ItMiIsInNwZWMiOnsidGVtcGxhdGUiOnsic3BlYyI6eyJjb250YWluZXJzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvby0yOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQtMjp2MC4yLjAifV19fX19XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJyZWxhdGVkSW1hZ2VzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvbzp2MC4yLjAiLCJuYW1lIjoib3BlcmF0b3IifSx7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLW90aGVyOnYwLjIuMCIsIm5hbWUiOiJvdGhlciJ9XSwicmVwbGFjZXMiOiJmb28udjAuMS4wIiwic2tpcHMiOlsiZm9vLnYwLjEuMSIsImZvby52MC4xLjIiXSwidmVyc2lvbiI6IjAuMi4wIn19 -relatedImages: -- image: test.registry/foo-operator/foo-2:v0.2.0 - name: "" -- image: test.registry/foo-operator/foo-bundle:v0.2.0 - name: "" -- image: test.registry/foo-operator/foo-init-2:v0.2.0 - name: "" -- image: test.registry/foo-operator/foo-init:v0.2.0 - name: "" -- image: test.registry/foo-operator/foo-other:v0.2.0 - name: other -- image: test.registry/foo-operator/foo:v0.2.0 - name: operator -schema: olm.bundle -` -} - -func migrateBarCatalogSqlite() string { - return `--- -defaultChannel: alpha -name: bar -schema: olm.package ---- -entries: -- name: bar.v0.1.0 -- name: bar.v0.2.0 - skipRange: <0.2.0 - skips: - - bar.v0.1.0 -name: alpha -package: bar -schema: olm.channel ---- -image: test.registry/bar-operator/bar-bundle:v0.1.0 -name: bar.v0.1.0 -package: bar -properties: -- type: olm.gvk - value: - group: test.bar - kind: Bar - version: v1alpha1 -- type: olm.package - value: - packageName: bar - version: 0.1.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= -relatedImages: -- image: test.registry/bar-operator/bar-bundle:v0.1.0 - name: "" -- image: test.registry/bar-operator/bar:v0.1.0 - name: operator -schema: olm.bundle ---- -image: test.registry/bar-operator/bar-bundle:v0.2.0 -name: bar.v0.2.0 -package: bar -properties: -- type: olm.gvk - value: - group: test.bar - kind: Bar - version: v1alpha1 -- type: olm.package - value: - packageName: bar - version: 0.2.0 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 -- type: olm.bundle.object - value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= -relatedImages: -- image: test.registry/bar-operator/bar-bundle:v0.2.0 - name: "" -- image: test.registry/bar-operator/bar:v0.2.0 - name: operator -schema: olm.bundle -` -} - func migrateFooCatalogFBC() string { return `--- defaultChannel: beta diff --git a/alpha/action/render.go b/alpha/action/render.go index a124c0f8a..a2ebbe96b 100644 --- a/alpha/action/render.go +++ b/alpha/action/render.go @@ -2,7 +2,6 @@ package action import ( "context" - "database/sql" "encoding/json" "errors" "fmt" @@ -10,11 +9,8 @@ import ( "path/filepath" "sort" "strings" - "sync" "text/template" - "github.com/h2non/filetype" - "github.com/h2non/filetype/matchers" "k8s.io/apimachinery/pkg/util/sets" "github.com/operator-framework/operator-registry/alpha/action/migrations" @@ -25,17 +21,12 @@ import ( "github.com/operator-framework/operator-registry/pkg/image/containersimageregistry" "github.com/operator-framework/operator-registry/pkg/lib/bundle" "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" ) -var logDeprecationMessage sync.Once - type RefType uint const ( RefBundleImage RefType = 1 << iota - RefSqliteImage - RefSqliteFile RefDCImage RefDCDir RefBundleDir @@ -55,15 +46,9 @@ type Render struct { AllowedRefMask RefType ImageRefTemplate *template.Template Migrations *migrations.Migrations - - skipSqliteDeprecationLog bool } func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { - if r.skipSqliteDeprecationLog { - // exhaust once with a no-op function. - logDeprecationMessage.Do(func() {}) - } if r.Registry == nil { reg, err := containersimageregistry.NewDefault() if err != nil { @@ -125,21 +110,8 @@ func (r Render) renderReference(ctx context.Context, ref string) (*declcfg.Decla } return declcfg.LoadFS(ctx, os.DirFS(ref)) } - // The only supported file type is an sqlite DB file, - // since declarative configs will be in a directory. - if err := checkDBFile(ref); err != nil { - return nil, err - } - if !r.AllowedRefMask.Allowed(RefSqliteFile) { - return nil, fmt.Errorf("cannot render sqlite file: %w", ErrNotAllowed) - } - - db, err := sqlite.Open(ref) - if err != nil { - return nil, err - } - defer db.Close() - return sqliteToDeclcfg(ctx, db) + // Only directories are supported for file-based catalogs and bundles. + return nil, fmt.Errorf("ref %q is not a directory", ref) } func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.DeclarativeConfig, error) { @@ -160,145 +132,61 @@ func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.D return nil, fmt.Errorf("failed to unpack image %q: %v", ref, err) } - var cfg *declcfg.DeclarativeConfig - // nolint:nestif - if dbFile, ok := labels[containertools.DbLocationLabel]; ok { - if !r.AllowedRefMask.Allowed(RefSqliteImage) { - return nil, fmt.Errorf("cannot render sqlite image: %w", ErrNotAllowed) - } - db, err := sqlite.Open(filepath.Join(tmpDir, dbFile)) - if err != nil { - return nil, err - } - defer db.Close() - cfg, err = sqliteToDeclcfg(ctx, db) - if err != nil { - return nil, err - } - } else if configsDir, ok := labels[containertools.ConfigsLocationLabel]; ok { - if !r.AllowedRefMask.Allowed(RefDCImage) { - return nil, fmt.Errorf("cannot render declarative config image: %w", ErrNotAllowed) - } - cfg, err = declcfg.LoadFS(ctx, os.DirFS(filepath.Join(tmpDir, configsDir))) - if err != nil { - return nil, err - } - } else if _, ok := labels[bundle.PackageLabel]; ok { - if !r.AllowedRefMask.Allowed(RefBundleImage) { - return nil, fmt.Errorf("cannot render bundle image: %w", ErrNotAllowed) - } - img, err := registry.NewImageInput(ref, tmpDir) - if err != nil { - return nil, err - } - - bundle, err := bundleToDeclcfg(img.Bundle) - if err != nil { - return nil, err - } - cfg = &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*bundle}} - } else { - labelKeys := sets.StringKeySet(labels) - labelVals := []string{} - for _, k := range labelKeys.List() { - labelVals = append(labelVals, fmt.Sprintf(" %s=%s", k, labels[k])) - } - if len(labelVals) > 0 { - return nil, fmt.Errorf("render %q: image type could not be determined, found labels\n%s", ref, strings.Join(labelVals, "\n")) - } else { - return nil, fmt.Errorf("render %q: image type could not be determined: image has no labels", ref) - } + cfg, err := r.renderImageConfig(ctx, ref, tmpDir, labels) + if err != nil { + return nil, err } return cfg, nil } -// checkDBFile returns an error if ref is not an sqlite3 database. -func checkDBFile(ref string) error { - typ, err := filetype.MatchFile(ref) - if err != nil { - return err +func (r Render) renderImageConfig(ctx context.Context, ref image.SimpleReference, tmpDir string, labels map[string]string) (*declcfg.DeclarativeConfig, error) { + if configsDir, ok := labels[containertools.ConfigsLocationLabel]; ok { + return r.renderDeclcfgImage(ctx, tmpDir, configsDir) } - if typ != matchers.TypeSqlite { - return fmt.Errorf("ref %q has unsupported file type: %s", ref, typ) + if _, ok := labels[bundle.PackageLabel]; ok { + return r.renderBundleImage(ref, tmpDir) } - return nil + return nil, r.imageTypeError(ref.String(), labels) } -func sqliteToDeclcfg(ctx context.Context, db *sql.DB) (*declcfg.DeclarativeConfig, error) { - logDeprecationMessage.Do(func() { - sqlite.LogSqliteDeprecation() - }) - - migrator, err := sqlite.NewSQLLiteMigrator(db) +func (r Render) renderDeclcfgImage(ctx context.Context, tmpDir, configsDir string) (*declcfg.DeclarativeConfig, error) { + if !r.AllowedRefMask.Allowed(RefDCImage) { + return nil, fmt.Errorf("cannot render declarative config image: %w", ErrNotAllowed) + } + cfg, err := declcfg.LoadFS(ctx, os.DirFS(filepath.Join(tmpDir, configsDir))) if err != nil { return nil, err } - if migrator == nil { - return nil, fmt.Errorf("failed to load migrator") - } + return cfg, nil +} - if err := migrator.Migrate(ctx); err != nil { - return nil, err +func (r Render) renderBundleImage(ref image.SimpleReference, tmpDir string) (*declcfg.DeclarativeConfig, error) { + if !r.AllowedRefMask.Allowed(RefBundleImage) { + return nil, fmt.Errorf("cannot render bundle image: %w", ErrNotAllowed) } - - q := sqlite.NewSQLLiteQuerierFromDb(db) - m, err := sqlite.ToModel(ctx, q) + img, err := registry.NewImageInput(ref, tmpDir) if err != nil { return nil, err } - cfg := declcfg.ConvertFromModel(m) - - if err := populateDBRelatedImages(ctx, &cfg, db); err != nil { + bundle, err := bundleToDeclcfg(img.Bundle) + if err != nil { return nil, err } - - return &cfg, nil + return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*bundle}}, nil } -func populateDBRelatedImages(ctx context.Context, cfg *declcfg.DeclarativeConfig, db *sql.DB) error { - rows, err := db.QueryContext(ctx, "SELECT image, operatorbundle_name FROM related_image") - if err != nil { - return err - } - defer rows.Close() - - // nolint:staticcheck - images := map[string]sets.String{} - for rows.Next() { - var ( - img sql.NullString - bundleName sql.NullString - ) - if err := rows.Scan(&img, &bundleName); err != nil { - return err - } - if !img.Valid || !bundleName.Valid { - continue - } - m, ok := images[bundleName.String] - if !ok { - m = sets.NewString() - } - m.Insert(img.String) - images[bundleName.String] = m +func (r Render) imageTypeError(ref string, labels map[string]string) error { + labelKeys := sets.StringKeySet(labels) + labelList := labelKeys.List() + labelVals := make([]string, 0, len(labelList)) + for _, k := range labelList { + labelVals = append(labelVals, fmt.Sprintf(" %s=%s", k, labels[k])) } - - for i, b := range cfg.Bundles { - ris, ok := images[b.Name] - if !ok { - continue - } - for _, ri := range b.RelatedImages { - if ris.Has(ri.Image) { - ris.Delete(ri.Image) - } - } - for ri := range ris { - cfg.Bundles[i].RelatedImages = append(cfg.Bundles[i].RelatedImages, declcfg.RelatedImage{Image: ri}) - } + if len(labelVals) > 0 { + return fmt.Errorf("render %q: image type could not be determined, found labels\n%s", ref, strings.Join(labelVals, "\n")) } - return nil + return fmt.Errorf("render %q: image type could not be determined: image has no labels", ref) } func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.Bundle, error) { diff --git a/alpha/action/render_test.go b/alpha/action/render_test.go index 59929f053..377d44e62 100644 --- a/alpha/action/render_test.go +++ b/alpha/action/render_test.go @@ -6,10 +6,7 @@ import ( "encoding/json" "fmt" "io/fs" - "os" - "path/filepath" "testing" - "testing/fstest" "text/template" "github.com/stretchr/testify/require" @@ -19,11 +16,8 @@ import ( "github.com/operator-framework/operator-registry/alpha/action/migrations" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - "github.com/operator-framework/operator-registry/pkg/containertools" "github.com/operator-framework/operator-registry/pkg/image" "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" ) type fauxMigration struct { @@ -78,13 +72,6 @@ func TestRender(t *testing.T) { foov2crdNoRelatedImages, err = yaml.ToJSON(foov2crdNoRelatedImages) require.NoError(t, err) - dir := t.TempDir() - dbFile := filepath.Join(dir, "index.db") - imageMap := map[image.Reference]string{ - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): "testdata/foo-bundle-v0.1.0", - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.2.0"): "testdata/foo-bundle-v0.2.0", - } - require.NoError(t, generateSqliteFile(dbFile, imageMap)) testMigrations := migrations.Migrations{ Migrations: []migrations.Migration{ fauxMigration{"faux-migration", "my help text", func(d *declcfg.DeclarativeConfig) error { @@ -97,376 +84,6 @@ func TestRender(t *testing.T) { } specs := []spec{ - { - name: "Success/SqliteIndexImage", - render: action.Render{ - Refs: []string{"test.registry/foo-operator/foo-index-sqlite:v0.2.0"}, - Registry: reg, - }, - expectCfg: &declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "foo", - DefaultChannel: "beta", - }, - }, - Channels: []declcfg.Channel{ - {Schema: "olm.channel", Package: "foo", Name: "beta", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - {Schema: "olm.channel", Package: "foo", Name: "stable", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - }, - Bundles: []declcfg.Bundle{ - { - Schema: "olm.bundle", - Name: "foo.v0.1.0", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.1.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov1crd), - property.MustBuildBundleObject(foov1csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.1.0", - }, - }, - CsvJSON: string(foov1csv), - Objects: []string{string(foov1csv), string(foov1crd)}, - }, - { - Schema: "olm.bundle", - Name: "foo.v0.2.0", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.2.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov2crd), - property.MustBuildBundleObject(foov2csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init:v0.2.0", - }, - { - Name: "other", - Image: "test.registry/foo-operator/foo-other:v0.2.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.2.0", - }, - }, - CsvJSON: string(foov2csv), - Objects: []string{string(foov2csv), string(foov2crd)}, - }, - }, - }, - assertion: require.NoError, - }, - { - name: "Success/SqliteIndexImageWithMigration", - render: action.Render{ - Refs: []string{"test.registry/foo-operator/foo-index-sqlite:v0.2.0"}, - Registry: reg, - Migrations: &testMigrations, - }, - expectCfg: &declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "foo", - DefaultChannel: "beta", - }, - }, - Channels: []declcfg.Channel{ - {Schema: "olm.channel", Package: "foo", Name: "beta", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - {Schema: "olm.channel", Package: "foo", Name: "stable", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - }, - Bundles: []declcfg.Bundle{ - { - Schema: "olm.bundle", - Name: "foo.v0.1.0-MIGRATED", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.1.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov1crd), - property.MustBuildBundleObject(foov1csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.1.0", - }, - }, - CsvJSON: string(foov1csv), - Objects: []string{string(foov1csv), string(foov1crd)}, - }, - { - Schema: "olm.bundle", - Name: "foo.v0.2.0-MIGRATED", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.2.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov2crd), - property.MustBuildBundleObject(foov2csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init:v0.2.0", - }, - { - Name: "other", - Image: "test.registry/foo-operator/foo-other:v0.2.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.2.0", - }, - }, - CsvJSON: string(foov2csv), - Objects: []string{string(foov2csv), string(foov2crd)}, - }, - }, - }, - assertion: require.NoError, - }, - { - name: "Success/SqliteFile", - render: action.Render{ - Refs: []string{dbFile}, - Registry: reg, - }, - expectCfg: &declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "foo", - DefaultChannel: "beta", - }, - }, - Channels: []declcfg.Channel{ - {Schema: "olm.channel", Package: "foo", Name: "beta", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - {Schema: "olm.channel", Package: "foo", Name: "stable", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - }, - Bundles: []declcfg.Bundle{ - { - Schema: "olm.bundle", - Name: "foo.v0.1.0", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.1.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov1crd), - property.MustBuildBundleObject(foov1csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.1.0", - }, - }, - CsvJSON: string(foov1csv), - Objects: []string{string(foov1csv), string(foov1crd)}, - }, - { - Schema: "olm.bundle", - Name: "foo.v0.2.0", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.2.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov2crd), - property.MustBuildBundleObject(foov2csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init:v0.2.0", - }, - { - Name: "other", - Image: "test.registry/foo-operator/foo-other:v0.2.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.2.0", - }, - }, - CsvJSON: string(foov2csv), - Objects: []string{string(foov2csv), string(foov2crd)}, - }, - }, - }, - assertion: require.NoError, - }, - { - name: "Success/SqliteFileMigration", - render: action.Render{ - Refs: []string{dbFile}, - Registry: reg, - Migrations: &testMigrations, - }, - expectCfg: &declcfg.DeclarativeConfig{ - Packages: []declcfg.Package{ - { - Schema: "olm.package", - Name: "foo", - DefaultChannel: "beta", - }, - }, - Channels: []declcfg.Channel{ - {Schema: "olm.channel", Package: "foo", Name: "beta", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - {Schema: "olm.channel", Package: "foo", Name: "stable", Entries: []declcfg.ChannelEntry{ - {Name: "foo.v0.1.0", SkipRange: "<0.1.0"}, - {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0", SkipRange: "<0.2.0", Skips: []string{"foo.v0.1.1", "foo.v0.1.2"}}, - }}, - }, - Bundles: []declcfg.Bundle{ - { - Schema: "olm.bundle", - Name: "foo.v0.1.0-MIGRATED", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.1.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov1crd), - property.MustBuildBundleObject(foov1csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-bundle:v0.1.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.1.0", - }, - }, - CsvJSON: string(foov1csv), - Objects: []string{string(foov1csv), string(foov1crd)}, - }, - { - Schema: "olm.bundle", - Name: "foo.v0.2.0-MIGRATED", - Package: "foo", - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - Properties: []property.Property{ - property.MustBuildGVK("test.foo", "v1", "Foo"), - property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), - property.MustBuildPackage("foo", "0.2.0"), - property.MustBuildPackageRequired("bar", "<0.1.0"), - property.MustBuildBundleObject(foov2crd), - property.MustBuildBundleObject(foov2csv), - }, - RelatedImages: []declcfg.RelatedImage{ - { - Image: "test.registry/foo-operator/foo-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-bundle:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init-2:v0.2.0", - }, - { - Image: "test.registry/foo-operator/foo-init:v0.2.0", - }, - { - Name: "other", - Image: "test.registry/foo-operator/foo-other:v0.2.0", - }, - { - Name: "operator", - Image: "test.registry/foo-operator/foo:v0.2.0", - }, - }, - CsvJSON: string(foov2csv), - Objects: []string{string(foov2csv), string(foov2crd)}, - }, - }, - }, - assertion: require.NoError, - }, { name: "Success/DeclcfgIndexImage", render: action.Render{ @@ -1227,51 +844,7 @@ func TestAllowRefMask(t *testing.T) { reg, err := newRegistry(t) require.NoError(t, err) - dir := t.TempDir() - dbFile := filepath.Join(dir, "index.db") - imageMap := map[image.Reference]string{ - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): "testdata/foo-bundle-v0.1.0", - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.2.0"): "testdata/foo-bundle-v0.2.0", - } - require.NoError(t, generateSqliteFile(dbFile, imageMap)) - specs := []spec{ - { - name: "SqliteImage/Allowed", - render: action.Render{ - Refs: []string{"test.registry/foo-operator/foo-index-sqlite:v0.2.0"}, - Registry: reg, - AllowedRefMask: action.RefSqliteImage, - }, - expectErr: nil, - }, - { - name: "SqliteImage/NotAllowed", - render: action.Render{ - Refs: []string{"test.registry/foo-operator/foo-index-sqlite:v0.2.0"}, - Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, - }, - expectErr: action.ErrNotAllowed, - }, - { - name: "SqliteFile/Allowed", - render: action.Render{ - Refs: []string{dbFile}, - Registry: reg, - AllowedRefMask: action.RefSqliteFile, - }, - expectErr: nil, - }, - { - name: "SqliteFile/NotAllowed", - render: action.Render{ - Refs: []string{dbFile}, - Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefBundleImage | action.RefBundleDir, - }, - expectErr: action.ErrNotAllowed, - }, { name: "DeclcfgImage/Allowed", render: action.Render{ @@ -1286,7 +859,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"test.registry/foo-operator/foo-index-declcfg:v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, + AllowedRefMask: action.RefDCDir | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -1304,7 +877,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"testdata/foo-index-v0.2.0-declcfg"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, + AllowedRefMask: action.RefDCImage | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -1322,7 +895,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"test.registry/foo-operator/foo-bundle:v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleDir, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -1340,25 +913,10 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"testdata/foo-bundle-v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefBundleImage, }, expectErr: action.ErrNotAllowed, }, - { - name: "All/Allowed", - render: action.Render{ - Refs: []string{ - "test.registry/foo-operator/foo-index-sqlite:v0.2.0", - dbFile, - "test.registry/foo-operator/foo-index-declcfg:v0.2.0", - "testdata/foo-index-v0.2.0-declcfg", - "test.registry/foo-operator/foo-bundle:v0.2.0", - "testdata/foo-bundle-v0.2.0", - }, - Registry: reg, - }, - expectErr: nil, - }, } for _, s := range specs { t.Run(s.name, func(t *testing.T) { @@ -1383,8 +941,6 @@ func TestAllowRefMaskAllowed(t *testing.T) { pass: []action.RefType{ action.RefDCImage, action.RefDCDir, - action.RefSqliteImage, - action.RefSqliteFile, action.RefBundleImage, action.RefBundleDir, }, @@ -1398,8 +954,6 @@ func TestAllowRefMaskAllowed(t *testing.T) { }, fail: []action.RefType{ action.RefDCDir, - action.RefSqliteImage, - action.RefSqliteFile, action.RefBundleImage, }, }, @@ -1411,8 +965,6 @@ func TestAllowRefMaskAllowed(t *testing.T) { action.RefDCDir, }, fail: []action.RefType{ - action.RefSqliteImage, - action.RefSqliteFile, action.RefBundleImage, }, }, @@ -1446,16 +998,7 @@ var bundleImageV2NoCSVRelatedImages embed.FS //go:embed testdata/foo-index-v0.2.0-declcfg/foo/* var declcfgImage embed.FS -func newRegistry(t *testing.T) (image.Registry, error) { - imageMap := map[image.Reference]string{ - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): "testdata/foo-bundle-v0.1.0", - image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.2.0"): "testdata/foo-bundle-v0.2.0", - } - - subSqliteImage, err := generateSqliteFS(t, imageMap) - if err != nil { - return nil, err - } +func newRegistry(_ *testing.T) (image.Registry, error) { subDeclcfgImage, err := fs.Sub(declcfgImage, "testdata/foo-index-v0.2.0-declcfg") if err != nil { return nil, err @@ -1474,12 +1017,6 @@ func newRegistry(t *testing.T) (image.Registry, error) { } return &image.MockRegistry{ RemoteImages: map[image.Reference]*image.MockImage{ - image.SimpleReference("test.registry/foo-operator/foo-index-sqlite:v0.2.0"): { - Labels: map[string]string{ - containertools.DbLocationLabel: "/database/index.db", - }, - FS: subSqliteImage, - }, image.SimpleReference("test.registry/foo-operator/foo-index-declcfg:v0.2.0"): { Labels: map[string]string{ "operators.operatorframework.io.index.configs.v1": "/foo", @@ -1507,56 +1044,3 @@ func newRegistry(t *testing.T) (image.Registry, error) { }, }, nil } - -func generateSqliteFS(t *testing.T, imageMap map[image.Reference]string) (fs.FS, error) { - dir := t.TempDir() - - dbFile := filepath.Join(dir, "index.db") - if err := generateSqliteFile(dbFile, imageMap); err != nil { - return nil, err - } - - dbData, err := os.ReadFile(dbFile) - if err != nil { - return nil, err - } - - return &fstest.MapFS{ - "database/index.db": &fstest.MapFile{ - Data: dbData, - }, - }, nil -} - -func generateSqliteFile(path string, imageMap map[image.Reference]string) error { - db, err := sqlite.Open(path) - if err != nil { - return err - } - defer db.Close() - - m, err := sqlite.NewSQLLiteMigrator(db) - if err != nil { - return err - } - if err := m.Migrate(context.Background()); err != nil { - return err - } - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - if err != nil { - return err - } - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - - loader, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return err - } - - populator := registry.NewDirectoryPopulator(loader, graphLoader, dbQuerier, imageMap, nil) - if err := populator.Populate(registry.ReplacesMode); err != nil { - return err - } - return nil -} diff --git a/alpha/declcfg/api_conversions.go b/alpha/declcfg/api_conversions.go new file mode 100644 index 000000000..622a6b8db --- /dev/null +++ b/alpha/declcfg/api_conversions.go @@ -0,0 +1,398 @@ +package declcfg + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "sort" + + "github.com/blang/semver/v4" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/api/pkg/lib/version" + "github.com/operator-framework/api/pkg/operators" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-registry/alpha/property" + "github.com/operator-framework/operator-registry/pkg/api" +) + +// ConvertBundleToAPIBundle converts a declcfg.Bundle to an api.Bundle. +// The pkg and channels parameters provide context needed for the conversion. +func ConvertBundleToAPIBundle(b Bundle, pkg Package, channels []Channel) (*api.Bundle, error) { + props, err := parseProperties(b.Properties) + if err != nil { + return nil, fmt.Errorf("parse properties: %v", err) + } + + csvJSON := generateCSVJSON(b, pkg, props) + + // Find which channel this bundle belongs to + channelName := "" + for _, ch := range channels { + if ch.Package != b.Package { + continue + } + for _, entry := range ch.Entries { + if entry.Name == b.Name { + channelName = ch.Name + break + } + } + if channelName != "" { + break + } + } + + // Get replaces and skips from channel entry + var replaces string + var skips []string + var skipRange string + for _, ch := range channels { + if ch.Package != b.Package { + continue + } + for _, entry := range ch.Entries { + if entry.Name == b.Name { + replaces = entry.Replaces + skips = entry.Skips + skipRange = entry.SkipRange + break + } + } + } + + apiDeps, err := convertPropertiesToAPIDependencies(b.Properties) + if err != nil { + return nil, fmt.Errorf("convert properties to api dependencies: %v", err) + } + + return &api.Bundle{ + CsvName: b.Name, + PackageName: b.Package, + ChannelName: channelName, + BundlePath: b.Image, + ProvidedApis: gvksProvidedtoAPIGVKs(props.GVKs), + RequiredApis: gvksRequiredtoAPIGVKs(props.GVKsRequired), + Version: props.Packages[0].Version, + SkipRange: skipRange, + Dependencies: apiDeps, + Properties: convertPropertiesToAPIProperties(b.Properties), + Replaces: replaces, + Skips: skips, + CsvJson: csvJSON, + Object: b.Objects, + }, nil +} + +func generateCSVJSON(b Bundle, pkg Package, props *property.Properties) string { + if b.CsvJSON != "" || len(props.CSVMetadatas) != 1 { + return b.CsvJSON + } + + csv := buildCSV(b, pkg, props) + csvData, err := json.Marshal(csv) + if err != nil { + return b.CsvJSON + } + csvJSON := string(csvData) + if len(b.Objects) == 0 { + b.Objects = []string{csvJSON} + } + return csvJSON +} + +func buildCSV(b Bundle, pkg Package, props *property.Properties) *v1alpha1.ClusterServiceVersion { + var icons []v1alpha1.Icon + if pkg.Icon != nil { + icons = []v1alpha1.Icon{{ + Data: base64.StdEncoding.EncodeToString(pkg.Icon.Data), + MediaType: pkg.Icon.MediaType, + }} + } + + csv := csvMetadataToCsv(props.CSVMetadatas[0]) + csv.Name = b.Name + csv.Spec.Icon = icons + csv.Spec.InstallStrategy = v1alpha1.NamedInstallStrategy{ + StrategyName: "deployment", + } + + ver, err := semver.Parse(props.Packages[0].Version) + if err == nil { + csv.Spec.Version = version.OperatorVersion{Version: ver} + } + + csv.Spec.RelatedImages = convertRelatedImagesToCSVRelatedImages(b.RelatedImages) + if csv.Spec.Description == "" { + csv.Spec.Description = pkg.Description + } + return &csv +} + +func parseProperties(in []property.Property) (*property.Properties, error) { + props, err := property.Parse(in) + if err != nil { + return nil, err + } + + if len(props.Packages) != 1 { + 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) []*api.GroupVersionKind { + // nolint:prealloc + var out []*api.GroupVersionKind + for _, gvk := range in { + out = append(out, &api.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }) + } + return out +} + +func gvksRequiredtoAPIGVKs(in []property.GVKRequired) []*api.GroupVersionKind { + // nolint:prealloc + var out []*api.GroupVersionKind + for _, gvk := range in { + out = append(out, &api.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }) + } + return out +} + +func convertPropertiesToAPIProperties(props []property.Property) []*api.Property { + // nolint:prealloc + var out []*api.Property + for _, prop := range props { + // NOTE: This is a special case filter to prevent problems with existing client implementations that + // project bundle properties into CSV annotations and store those CSVs in a size-constrained + // storage backend (e.g. etcd via kube-apiserver). If the bundle object property has data inlined + // 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 || prop.Type == property.TypeCSVMetadata { + continue + } + + out = append(out, &api.Property{ + Type: prop.Type, + Value: string(prop.Value), + }) + } + return out +} + +func convertPropertiesToAPIDependencies(props []property.Property) ([]*api.Dependency, error) { + // nolint:prealloc + var out []*api.Dependency + for _, prop := range props { + switch prop.Type { + case property.TypeGVKRequired: + out = append(out, &api.Dependency{ + Type: property.TypeGVK, + Value: string(prop.Value), + }) + case property.TypePackageRequired: + var v property.PackageRequired + if err := json.Unmarshal(prop.Value, &v); err != nil { + return nil, err + } + pkg := property.MustBuildPackage(v.PackageName, v.VersionRange) + out = append(out, &api.Dependency{ + Type: pkg.Type, + Value: string(pkg.Value), + }) + } + } + return out, nil +} + +func convertRelatedImagesToCSVRelatedImages(in []RelatedImage) []v1alpha1.RelatedImage { + // nolint:prealloc + var out []v1alpha1.RelatedImage + for _, ri := range in { + out = append(out, v1alpha1.RelatedImage{ + Name: ri.Name, + Image: ri.Image, + }) + } + return out +} + +// ConvertAPIBundleToBundle converts an api.Bundle to a declcfg.Bundle. +func ConvertAPIBundleToBundle(b *api.Bundle) (*Bundle, error) { + bundleProps, err := convertAPIBundleToProperties(b) + if err != nil { + return nil, fmt.Errorf("convert properties: %v", err) + } + + relatedImages, err := getRelatedImages(b.CsvJson) + if err != nil { + return nil, fmt.Errorf("get related images: %v", err) + } + + return &Bundle{ + Schema: SchemaBundle, + Name: b.CsvName, + Package: b.PackageName, + Image: b.BundlePath, + Properties: bundleProps, + RelatedImages: relatedImages, + CsvJSON: b.CsvJson, + Objects: b.Object, + }, nil +} + +func convertAPIBundleToProperties(b *api.Bundle) ([]property.Property, error) { + // nolint:prealloc + var out []property.Property + + providedGVKs := map[property.GVK]struct{}{} + requiredGVKs := map[property.GVKRequired]struct{}{} + + foundPackageProperty := false + for i, p := range b.Properties { + switch p.Type { + case property.TypeGVK: + var v api.GroupVersionKind + if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { + return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + } + k := property.GVK{Group: v.Group, Kind: v.Kind, Version: v.Version} + providedGVKs[k] = struct{}{} + case property.TypePackage: + foundPackageProperty = true + out = append(out, property.Property{ + Type: property.TypePackage, + Value: json.RawMessage(p.Value), + }) + default: + out = append(out, property.Property{ + Type: p.Type, + Value: json.RawMessage(p.Value), + }) + } + } + + for i, p := range b.Dependencies { + switch p.Type { + case property.TypeGVK: + var v api.GroupVersionKind + if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { + return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + } + k := property.GVKRequired{Group: v.Group, Kind: v.Kind, Version: v.Version} + requiredGVKs[k] = struct{}{} + case property.TypePackage: + var v property.Package + if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { + return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + } + out = append(out, property.MustBuildPackageRequired(v.PackageName, v.Version)) + } + } + + if !foundPackageProperty { + out = append(out, property.MustBuildPackage(b.PackageName, b.Version)) + } + + for _, p := range b.ProvidedApis { + k := property.GVK{Group: p.Group, Kind: p.Kind, Version: p.Version} + if _, ok := providedGVKs[k]; !ok { + providedGVKs[k] = struct{}{} + } + } + for _, p := range b.RequiredApis { + k := property.GVKRequired{Group: p.Group, Kind: p.Kind, Version: p.Version} + if _, ok := requiredGVKs[k]; !ok { + requiredGVKs[k] = struct{}{} + } + } + + for p := range providedGVKs { + out = append(out, property.MustBuildGVK(p.Group, p.Version, p.Kind)) + } + + for p := range requiredGVKs { + out = append(out, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) + } + + for _, obj := range b.Object { + out = append(out, property.MustBuildBundleObject([]byte(obj))) + } + + sort.Slice(out, func(i, j int) bool { + if out[i].Type != out[j].Type { + return out[i].Type < out[j].Type + } + return string(out[i].Value) < string(out[j].Value) + }) + + return out, nil +} + +func getRelatedImages(csvJSON string) ([]RelatedImage, error) { + if len(csvJSON) == 0 { + return nil, nil + } + type csv struct { + Spec struct { + RelatedImages []struct { + Name string `json:"name"` + Image string `json:"image"` + } `json:"relatedImages"` + } `json:"spec"` + } + c := csv{} + if err := json.Unmarshal([]byte(csvJSON), &c); err != nil { + return nil, fmt.Errorf("unmarshal csv: %v", err) + } + var relatedImages []RelatedImage + for _, ri := range c.Spec.RelatedImages { + relatedImages = append(relatedImages, RelatedImage{ + Name: ri.Name, + Image: ri.Image, + }) + } + return relatedImages, nil +} diff --git a/alpha/declcfg/declcfg.go b/alpha/declcfg/declcfg.go index 83c7dc7ab..685febf05 100644 --- a/alpha/declcfg/declcfg.go +++ b/alpha/declcfg/declcfg.go @@ -12,20 +12,11 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" prettyunmarshaler "github.com/operator-framework/operator-registry/pkg/prettyunmarshaler" "github.com/operator-framework/operator-registry/pkg/registry" ) -// Re-export VersionRelease/Release types/functions from model package to make it possible for users to only include this package and avoid import cycles -type ( - Release = model.Release - VersionRelease = model.VersionRelease -) - -var NewRelease = model.NewRelease - const ( SchemaPackage = "olm.package" SchemaChannel = "olm.channel" diff --git a/alpha/declcfg/declcfg_to_model.go b/alpha/declcfg/declcfg_to_model.go deleted file mode 100644 index 318113e0f..000000000 --- a/alpha/declcfg/declcfg_to_model.go +++ /dev/null @@ -1,290 +0,0 @@ -package declcfg - -import ( - "fmt" - - "github.com/blang/semver/v4" - "go.podman.io/image/v5/docker/reference" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { - mpkgs := model.Model{} - defaultChannels := map[string]string{} - for _, p := range cfg.Packages { - if p.Name == "" { - return nil, fmt.Errorf("config contains package with no name") - } - - if _, ok := mpkgs[p.Name]; ok { - return nil, fmt.Errorf("duplicate package %q", p.Name) - } - - if errs := validation.IsDNS1123Label(p.Name); len(errs) > 0 { - return nil, fmt.Errorf("invalid package name %q: %v", p.Name, errs) - } - - mpkg := &model.Package{ - Name: p.Name, - Description: p.Description, - Channels: map[string]*model.Channel{}, - } - if p.Icon != nil { - mpkg.Icon = &model.Icon{ - Data: p.Icon.Data, - MediaType: p.Icon.MediaType, - } - } - defaultChannels[p.Name] = p.DefaultChannel - mpkgs[p.Name] = mpkg - } - - channelDefinedEntries := map[string]sets.Set[string]{} - for _, c := range cfg.Channels { - mpkg, ok := mpkgs[c.Package] - if !ok { - return nil, fmt.Errorf("unknown package %q for channel %q", c.Package, c.Name) - } - - if c.Name == "" { - return nil, fmt.Errorf("package %q contains channel with no name", c.Package) - } - - if _, ok := mpkg.Channels[c.Name]; ok { - return nil, fmt.Errorf("package %q has duplicate channel %q", c.Package, c.Name) - } - - mch := &model.Channel{ - Package: mpkg, - Name: c.Name, - Bundles: map[string]*model.Bundle{}, - // NOTICE: The field Properties of the type Channel is 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. - Properties: c.Properties, - } - - cde := sets.Set[string]{} - for _, entry := range c.Entries { - if _, ok := mch.Bundles[entry.Name]; ok { - return nil, fmt.Errorf("invalid package %q, channel %q: duplicate entry %q", c.Package, c.Name, entry.Name) - } - cde = cde.Insert(entry.Name) - mch.Bundles[entry.Name] = &model.Bundle{ - Package: mpkg, - Channel: mch, - Name: entry.Name, - Replaces: entry.Replaces, - Skips: entry.Skips, - SkipRange: entry.SkipRange, - } - } - channelDefinedEntries[c.Package] = cde - - mpkg.Channels[c.Name] = mch - - defaultChannelName := defaultChannels[c.Package] - if defaultChannelName == c.Name { - mpkg.DefaultChannel = mch - } - } - - // packageBundles tracks the set of bundle names for each package - // and is used to detect duplicate bundles. - packageBundles := map[string]sets.Set[string]{} - - for _, b := range cfg.Bundles { - if b.Package == "" { - return nil, fmt.Errorf("package name must be set for bundle %q", b.Name) - } - mpkg, ok := mpkgs[b.Package] - if !ok { - return nil, fmt.Errorf("unknown package %q for bundle %q", b.Package, b.Name) - } - - bundles, ok := packageBundles[b.Package] - if !ok { - bundles = sets.Set[string]{} - } - if bundles.Has(b.Name) { - return nil, fmt.Errorf("package %q has duplicate bundle %q", b.Package, b.Name) - } - bundles.Insert(b.Name) - packageBundles[b.Package] = bundles - - props, err := property.Parse(b.Properties) - if err != nil { - return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) - } - - if len(props.Packages) != 1 { - return nil, fmt.Errorf("package %q bundle %q must have exactly 1 %q property, found %d", b.Package, b.Name, property.TypePackage, len(props.Packages)) - } - - if b.Package != props.Packages[0].PackageName { - return nil, fmt.Errorf("package %q does not match %q property %q", b.Package, property.TypePackage, props.Packages[0].PackageName) - } - - if err := validateImagePullSpec(b.Image, "package %q bundle %q image", b.Package, b.Name); err != nil { - return nil, err - } - for i, rel := range b.RelatedImages { - if err := validateImagePullSpec(rel.Image, "package %q bundle %q relatedImages[%d].image", b.Package, b.Name, i); err != nil { - return nil, err - } - } - - // Parse version from the package property. - rawVersion := props.Packages[0].Version - ver, err := semver.Parse(rawVersion) - if err != nil { - return nil, fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err) - } - - // Parse release version from the package property. - var relver model.Release - if props.Packages[0].Release != "" { - relver, err = model.NewRelease(props.Packages[0].Release) - if err != nil { - return nil, fmt.Errorf("error parsing bundle %q release version %q: %v", b.Name, props.Packages[0].Release, err) - } - } - - channelDefinedEntries[b.Package] = channelDefinedEntries[b.Package].Delete(b.Name) - found := false - for _, mch := range mpkg.Channels { - if mb, ok := mch.Bundles[b.Name]; ok { - found = true - mb.Image = b.Image - mb.Properties = b.Properties - mb.RelatedImages = relatedImagesToModelRelatedImages(b.RelatedImages) - mb.CsvJSON = b.CsvJSON - mb.Objects = b.Objects - mb.PropertiesP = props - mb.Version = ver - // TODO: Jordan: follow-up will evolve the internal types for more consistent use of VersionRelease - mb.Release = semver.Version{Pre: relver} - } - } - if !found { - return nil, fmt.Errorf("package %q, bundle %q not found in any channel entries", b.Package, b.Name) - } - } - - for pkg, entries := range channelDefinedEntries { - if entries.Len() > 0 { - return nil, fmt.Errorf("no olm.bundle blobs found in package %q for olm.channel entries %s", pkg, sets.List[string](entries)) - } - } - - for _, mpkg := range mpkgs { - defaultChannelName := defaultChannels[mpkg.Name] - if defaultChannelName != "" && mpkg.DefaultChannel == nil { - dch := &model.Channel{ - Package: mpkg, - Name: defaultChannelName, - Bundles: map[string]*model.Bundle{}, - } - mpkg.DefaultChannel = dch - mpkg.Channels[dch.Name] = dch - } - } - - // deprecationsByPackage tracks the set of package names - // and is used to detect duplicate packages. - deprecationsByPackage := sets.New[string]() - - for i, deprecation := range cfg.Deprecations { - // no need to validate schema, since it could not be unmarshaled if missing/invalid - - if deprecation.Package == "" { - return nil, fmt.Errorf("package name must be set for deprecation item %v", i) - } - - // must refer to package in this catalog - mpkg, ok := mpkgs[deprecation.Package] - if !ok { - return nil, fmt.Errorf("cannot apply deprecations to an unknown package %q", deprecation.Package) - } - - // must be unique per package - if deprecationsByPackage.Has(deprecation.Package) { - return nil, fmt.Errorf("expected a maximum of one deprecation per package: %q", deprecation.Package) - } - deprecationsByPackage.Insert(deprecation.Package) - - references := sets.New[PackageScopedReference]() - - for j, entry := range deprecation.Entries { - if entry.Reference.Schema == "" { - return nil, fmt.Errorf("schema must be set for deprecation entry [%v] for package %q", deprecation.Package, j) - } - - if references.Has(entry.Reference) { - return nil, fmt.Errorf("duplicate deprecation entry %#v for package %q", entry.Reference, deprecation.Package) - } - references.Insert(entry.Reference) - - switch entry.Reference.Schema { - case SchemaBundle: - if !packageBundles[deprecation.Package].Has(entry.Reference.Name) { - return nil, fmt.Errorf("cannot deprecate bundle %q for package %q: bundle not found", entry.Reference.Name, deprecation.Package) - } - for _, mch := range mpkg.Channels { - if mb, ok := mch.Bundles[entry.Reference.Name]; ok { - mb.Deprecation = &model.Deprecation{Message: entry.Message} - } - } - case SchemaChannel: - ch, ok := mpkg.Channels[entry.Reference.Name] - if !ok { - return nil, fmt.Errorf("cannot deprecate channel %q for package %q: channel not found", entry.Reference.Name, deprecation.Package) - } - ch.Deprecation = &model.Deprecation{Message: entry.Message} - - case SchemaPackage: - if entry.Reference.Name != "" { - return nil, fmt.Errorf("package name must be empty for deprecated package %q (specified %q)", deprecation.Package, entry.Reference.Name) - } - mpkg.Deprecation = &model.Deprecation{Message: entry.Message} - - default: - return nil, fmt.Errorf("cannot deprecate object %#v referenced by entry %v for package %q: object schema unknown", entry.Reference, j, deprecation.Package) - } - } - } - - if err := mpkgs.Validate(); err != nil { - return nil, err - } - mpkgs.Normalize() - return mpkgs, nil -} - -func relatedImagesToModelRelatedImages(in []RelatedImage) []model.RelatedImage { - // nolint:prealloc - var out []model.RelatedImage - for _, p := range in { - out = append(out, model.RelatedImage{ - Name: p.Name, - Image: p.Image, - }) - } - return out -} - -// validateImagePullSpec checks that a non-empty image pull spec is valid -// Empty pull specs are not validated. -func validateImagePullSpec(pullSpec, errFormat string, errArgs ...interface{}) error { - if pullSpec == "" { - return nil - } - if _, err := reference.ParseNormalizedNamed(pullSpec); err != nil { - return fmt.Errorf(errFormat+": invalid image pull spec %q: %w", append(errArgs, pullSpec, err)...) - } - return nil -} diff --git a/alpha/declcfg/declcfg_to_model_test.go b/alpha/declcfg/declcfg_to_model_test.go deleted file mode 100644 index 3312d8a92..000000000 --- a/alpha/declcfg/declcfg_to_model_test.go +++ /dev/null @@ -1,655 +0,0 @@ -package declcfg - -import ( - "encoding/json" - "testing" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func TestConvertToModel(t *testing.T) { - type spec struct { - name string - cfg DeclarativeConfig - assertion require.ErrorAssertionFunc - } - - specs := []spec{ - { - name: "Error/PackageNoName", - assertion: hasError(`config contains package with no name`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("", "alpha", svgSmallCircle)}, - Bundles: []Bundle{{Name: "foo.v0.1.0"}}, - }, - }, - { - name: "Error/BundleMissingPackageName", - assertion: hasError(`package name must be set for bundle "foo.v0.1.0"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{{Name: "foo.v0.1.0"}}, - }, - }, - { - name: "Error/BundleUnknownPackage", - assertion: hasError(`unknown package "bar" for bundle "bar.v0.1.0"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("bar", "0.1.0")}, - }, - }, - { - name: "Error/BundleMissingChannel", - assertion: hasError(`package "foo", bundle "foo.v0.1.0" not found in any channel entries`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/BundleInvalidProperties", - assertion: hasError(`parse properties for bundle "foo.v0.1.0": parse property[2] of type "olm.foo": unexpected end of JSON input`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = append(b.Properties, property.Property{ - Type: "olm.foo", - Value: json.RawMessage("{"), - }) - })}, - }, - }, - { - name: "Error/BundlePackageMismatch", - assertion: hasError(`package "foo" does not match "olm.package" property "foooperator"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackage("foooperator", "0.1.0"), - } - })}, - }, - }, - { - name: "Error/BundleInvalidVersion", - assertion: hasError(`error parsing bundle "foo.v0.1.0" version "0.1.0.1": Invalid character(s) found in patch number "0.1"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackage("foo", "0.1.0.1"), - } - })}, - }, - }, - { - name: "Error/BundleMissingVersion", - assertion: hasError(`error parsing bundle "foo.v" version "": Version string empty`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "", func(b *Bundle) {})}, - }, - }, - { - name: "Error/PackageMissingDefaultChannel", - assertion: hasError(`invalid index: -└── invalid package "foo": - └── default channel must be set`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "bar", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/PackageNonExistentDefaultChannel", - assertion: hasError(`invalid index: -└── invalid package "foo": - └── invalid channel "bar": - └── channel must contain at least one bundle`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "bar", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "bar")}, - }, - }, - { - name: "Error/BundleMissingPackageProperty", - assertion: hasError(`package "foo" bundle "foo.v0.1.0" must have exactly 1 "olm.package" property, found 0`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", withNoProperties())}, - }, - }, - { - name: "Error/BundleMultiplePackageProperty", - assertion: hasError(`package "foo" bundle "foo.v0.1.0" must have exactly 1 "olm.package" property, found 2`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackage("foo", "0.1.0"), - property.MustBuildPackage("foo", "0.1.0"), - } - })}, - }, - }, - { - name: "Success/BundleWithDataButMissingImage", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", withNoBundleImage())}, - }, - }, - { - name: "Error/ChannelEntryWithoutBundle", - assertion: hasError(`no olm.bundle blobs found in package "foo" for olm.channel entries [foo.v0.1.0]`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - }, - }, - { - name: "Error/BundleWithoutChannelEntry", - assertion: hasError(`package "foo", bundle "foo.v0.2.0" not found in any channel entries`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.2.0")}, - }, - }, - { - name: "Error/ChannelMissingName", - assertion: hasError(`package "foo" contains channel with no name`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.2.0")}, - }, - }, - { - name: "Error/ChannelMissingPackageName", - assertion: hasError(`unknown package "" for channel "alpha"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.2.0")}, - }, - }, - { - name: "Error/ChannelNonExistentPackage", - assertion: hasError(`unknown package "non-existent" for channel "alpha"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("non-existent", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/ChannelDuplicateEntry", - assertion: hasError(`invalid package "foo", channel "alpha": duplicate entry "foo.v0.1.0"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", - ChannelEntry{Name: "foo.v0.1.0"}, - ChannelEntry{Name: "foo.v0.1.0"}, - )}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/DuplicatePackage", - assertion: hasError(`duplicate package "foo"`), - cfg: DeclarativeConfig{ - Packages: []Package{ - newTestPackage("foo", "alpha", svgSmallCircle), - newTestPackage("foo", "alpha", svgSmallCircle), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/PackageBreaksRFC1123", - assertion: hasError(`invalid package name "foo.bar": [must not contain dots]`), - cfg: DeclarativeConfig{ - Packages: []Package{ - newTestPackage("foo.bar", "alpha", svgSmallCircle), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/DuplicateChannel", - assertion: hasError(`package "foo" has duplicate channel "alpha"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{ - newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"}), - newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"}), - }, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/DuplicateBundle", - assertion: hasError(`package "foo" has duplicate bundle "foo.v0.1.0"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{ - newTestBundle("foo", "0.1.0"), - newTestBundle("foo", "0.1.0"), - }, - }, - }, - { - name: "Success/ValidModel", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Success/ValidModelWithChannelProperties", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{ - addChannelProperties( - newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"}), - []property.Property{ - {Type: "user", Value: json.RawMessage("{\"group\":\"xyz.com\",\"name\":\"account\"}")}, - }, - ), - }, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Success/ValidModelWithPackageProperties", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - }, - }, - { - name: "Error/Deprecation/UnspecifiedPackage", - assertion: hasError(`package name must be set for deprecation item 0`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - {Schema: SchemaDeprecation}, - }, - }, - }, - { - name: "Error/Deprecation/OutOfBoundsBundle", - assertion: hasError(`cannot deprecate bundle "foo.v2.0.0" for package "foo": bundle not found`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - { - Schema: SchemaDeprecation, - Package: "foo", - Entries: []DeprecationEntry{ - {Reference: PackageScopedReference{Schema: SchemaBundle, Name: "foo.v2.0.0"}, Message: "foo.v2.0.0 doesn't exist in the first place"}, - }, - }, - }, - }, - }, - { - name: "Error/Deprecation/OutOfBoundsPackage", - assertion: hasError(`cannot apply deprecations to an unknown package "nyarl"`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - { - Schema: SchemaDeprecation, - Package: "nyarl", - }, - }, - }, - }, - { - name: "Error/Deprecation/MultiplePerPackage", - assertion: hasError(`expected a maximum of one deprecation per package: "foo"`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - { - Schema: SchemaDeprecation, - Package: "foo", - Entries: []DeprecationEntry{ - {Reference: PackageScopedReference{Schema: SchemaChannel, Name: "alpha"}, Message: "no more alpha channel"}, - }, - }, - { - Schema: SchemaDeprecation, - Package: "foo", - Entries: []DeprecationEntry{ - {Reference: PackageScopedReference{Schema: SchemaBundle, Name: "foo.v0.1.0"}, Message: "foo.v0.1.0 is dead. do another thing"}, - }, - }, - }, - }, - }, - { - name: "Error/Deprecation/BadRefSchema", - assertion: hasError(`cannot deprecate object declcfg.PackageScopedReference{Schema:"badschema", Name:"foo.v2.0.0"} referenced by entry 0 for package "foo": object schema unknown`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - { - Schema: SchemaDeprecation, - Package: "foo", - Entries: []DeprecationEntry{ - {Reference: PackageScopedReference{Schema: "badschema", Name: "foo.v2.0.0"}, Message: "foo.v2.0.0 doesn't exist in the first place"}, - }, - }, - }, - }, - }, - { - name: "Error/Deprecation/DuplicateRef", - assertion: hasError(`duplicate deprecation entry declcfg.PackageScopedReference{Schema:"olm.bundle", Name:"foo.v0.1.0"} for package "foo"`), - cfg: DeclarativeConfig{ - Packages: []Package{ - addPackageProperties( - newTestPackage("foo", "alpha", svgSmallCircle), - []property.Property{ - {Type: "owner", Value: json.RawMessage("{\"group\":\"abc.com\",\"name\":\"admin\"}")}, - }, - ), - }, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - Deprecations: []Deprecation{ - { - Schema: SchemaDeprecation, - Package: "foo", - Entries: []DeprecationEntry{ - {Reference: PackageScopedReference{Schema: SchemaBundle, Name: "foo.v0.1.0"}, Message: "foo.v0.1.0 is bad"}, - {Reference: PackageScopedReference{Schema: SchemaBundle, Name: "foo.v0.1.0"}, Message: "foo.v0.1.0 is bad"}, - }, - }, - }, - }, - }, - { - name: "Error/InvalidReleaseVersion", - assertion: hasError(`error parsing bundle "foo.v0.1.0" release version "!!!": invalid release "!!!": segment 0: Invalid character(s) found in prerelease "!!!"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackageRelease("foo", "0.1.0", "!!!"), - } - })}, - }, - }, - { - name: "Error/InvalidBundleNormalizedName", - assertion: hasError(`invalid index: -└── invalid package "foo": - └── invalid channel "alpha": - └── invalid bundle "foo.v0.1.0-alpha.1.0.0": - └── name "foo.v0.1.0-alpha.1.0.0" does not match normalized name "foo-v0.1.0-alpha.1.0.0"`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0-alpha.1.0.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackageRelease("foo", "0.1.0", "alpha.1.0.0"), - } - b.Name = "foo.v0.1.0-alpha.1.0.0" - })}, - }, - }, - { - name: "Success/ValidBundleReleaseVersion", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo-v0.1.0-alpha.1.0.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackageRelease("foo", "0.1.0", "alpha.1.0.0"), - } - b.Name = "foo-v0.1.0-alpha.1.0.0" - })}, - }, - }, - { - name: "Error/BundleReleaseWithBuildMetadata", - assertion: hasError(`invalid index: -└── invalid package "foo": - └── invalid channel "alpha": - └── invalid bundle "foo.v0.1.0+alpha.1.0.0-0.0.1": - ├── name "foo.v0.1.0+alpha.1.0.0-0.0.1" does not match normalized name "foo-v0.1.0+alpha.1.0.0-0.0.1" - └── cannot use build metadata in version with a release version`), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0+alpha.1.0.0-0.0.1"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Properties = []property.Property{ - property.MustBuildPackageRelease("foo", "0.1.0+alpha.1.0.0", "0.0.1"), - } - b.Name = "foo.v0.1.0+alpha.1.0.0-0.0.1" - })}, - }, - }, - { - name: "Error/BundleImageInvalidPullSpecUnsupportedDigestSsha256", - assertion: hasErrorContaining("invalid image pull spec"), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - // Misspelled digest algorithm: ssha256 instead of sha256 (unsupported hash type) - b.Image = "quay.io/operator-framework/foo-bundle@ssha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" - })}, - }, - }, - { - name: "Error/BundleImageInvalidPullSpecUnsupportedDigestMd5", - assertion: hasErrorContaining("invalid image pull spec"), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Image = "quay.io/operator-framework/foo-bundle@md5:abcd1234abcd1234abcd1234abcd1234" - })}, - }, - }, - { - name: "Error/BundleRelatedImageInvalidPullSpecSsha256", - assertion: hasErrorContaining("invalid image pull spec"), - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.RelatedImages = []RelatedImage{ - {Name: "bundle", Image: testBundleImage("foo", "0.1.0")}, - {Name: "operator", Image: "quay.io/operator-framework/my-operator@ssha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}, - } - })}, - }, - }, - { - name: "Success/BundleImageValidSha256Digest", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Image = "quay.io/operator-framework/foo-bundle@sha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" - b.RelatedImages = []RelatedImage{ - {Name: "bundle", Image: "quay.io/operator-framework/foo-bundle@sha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}, - } - })}, - }, - }, - { - name: "Success/BundleImageValidTagWithDigest", - assertion: require.NoError, - cfg: DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { - b.Image = "quay.io/operator-framework/foo-bundle:v0.1.0@sha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" - b.RelatedImages = []RelatedImage{ - {Name: "bundle", Image: "quay.io/operator-framework/foo-bundle:v0.1.0@sha256:abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}, - } - })}, - }, - }, - } - - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - _, err := ConvertToModel(s.cfg) - s.assertion(t, err) - }) - } -} - -func TestConvertToModelBundle(t *testing.T) { - cfg := DeclarativeConfig{ - Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, - Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, - Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, - } - m, err := ConvertToModel(cfg) - require.NoError(t, err) - - pkg, ok := m["foo"] - require.True(t, ok, "expected package 'foo' to be present") - ch, ok := pkg.Channels["alpha"] - require.True(t, ok, "expected channel 'alpha' to be present") - b, ok := ch.Bundles["foo.v0.1.0"] - require.True(t, ok, "expected bundle 'foo.v0.1.0' to be present") - - assert.Equal(t, pkg, b.Package) - assert.Equal(t, ch, b.Channel) - assert.Equal(t, "foo.v0.1.0", b.Name) - assert.Equal(t, "foo-bundle:v0.1.0", b.Image) - assert.Empty(t, b.Replaces) - assert.Nil(t, b.Skips) - assert.Empty(t, b.SkipRange) - assert.Len(t, b.Properties, 3) - assert.Equal(t, []model.RelatedImage{{Name: "bundle", Image: "foo-bundle:v0.1.0"}}, b.RelatedImages) - assert.Nil(t, b.Deprecation) - assert.Len(t, b.Objects, 2) - assert.NotEmpty(t, b.CsvJSON) - assert.NotNil(t, b.PropertiesP) - assert.Len(t, b.PropertiesP.BundleObjects, 2) - assert.Len(t, b.PropertiesP.Packages, 1) - assert.Equal(t, semver.MustParse("0.1.0"), b.Version) -} - -func TestConvertToModelRoundtrip(t *testing.T) { - expected := buildValidDeclarativeConfig(validDeclarativeConfigSpec{IncludeUnrecognized: true, IncludeDeprecations: false}) // TODO: turn on deprecation when we have model-->declcfg conversion - - m, err := ConvertToModel(expected) - require.NoError(t, err) - actual := ConvertFromModel(m) - - removeJSONWhitespace(&expected) - removeJSONWhitespace(&actual) - - assert.Equal(t, expected.Packages, actual.Packages) - assert.Equal(t, expected.Bundles, actual.Bundles) - assert.Empty(t, actual.Others, "expected unrecognized schemas not to make the roundtrip") -} - -func hasError(expectedError string) require.ErrorAssertionFunc { - return func(t require.TestingT, actualError error, args ...interface{}) { - if stdt, ok := t.(*testing.T); ok { - stdt.Helper() - } - if actualError != nil && actualError.Error() == expectedError { - return - } - t.Errorf("expected error to be `%s`, got `%s`", expectedError, actualError) - t.FailNow() - } -} - -// hasErrorContaining returns an ErrorAssertionFunc that passes when the error message contains the given substring. -func hasErrorContaining(substring string) require.ErrorAssertionFunc { - return func(t require.TestingT, actualError error, args ...interface{}) { - if stdt, ok := t.(*testing.T); ok { - stdt.Helper() - } - require.Error(t, actualError) - require.Contains(t, actualError.Error(), substring, "expected error to contain %q", substring) - } -} diff --git a/alpha/declcfg/helpers_test.go b/alpha/declcfg/helpers_test.go index 9df5beebb..2ae660e57 100644 --- a/alpha/declcfg/helpers_test.go +++ b/alpha/declcfg/helpers_test.go @@ -6,11 +6,9 @@ import ( "sort" "testing" - "github.com/blang/semver/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" ) @@ -145,7 +143,7 @@ func withNoBundleData() func(*Bundle) { } } -func newTestBundle(packageName, version string, opts ...bundleOpt) Bundle { +func newTestBundle(packageName, version string) Bundle { csvJSON := fmt.Sprintf(`{"kind": "ClusterServiceVersion", "apiVersion": "operators.coreos.com/v1alpha1", "metadata":{"name":%q}}`, testBundleName(packageName, version)) b := Bundle{ Schema: SchemaBundle, @@ -169,9 +167,6 @@ func newTestBundle(packageName, version string, opts ...bundleOpt) Bundle { `{"kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1"}`, }, } - for _, opt := range opts { - opt(&b) - } sort.Slice(b.Properties, func(i, j int) bool { if b.Properties[i].Type != b.Properties[j].Type { return b.Properties[i].Type < b.Properties[j].Type @@ -216,39 +211,6 @@ func addChannelProperties(in Channel, p []property.Property) Channel { return in } -func buildTestModel() model.Model { - return model.Model{ - "anakin": buildAnakinPkgModel(), - "boba-fett": buildBobaFettPkgModel(), - } -} - -func getBundle(pkg *model.Package, ch *model.Channel, version, replaces string, skips ...string) *model.Bundle { - return &model.Bundle{ - Package: pkg, - Channel: ch, - Name: testBundleName(pkg.Name, version), - Image: testBundleImage(pkg.Name, version), - Properties: []property.Property{ - property.MustBuildPackage(pkg.Name, version), - property.MustBuildBundleObject([]byte(getCSVJson(pkg.Name, version))), - property.MustBuildBundleObject([]byte(getCRDJSON())), - }, - Replaces: replaces, - Skips: skips, - RelatedImages: []model.RelatedImage{{ - Name: "bundle", - Image: testBundleImage(pkg.Name, version), - }}, - CsvJSON: getCSVJson(pkg.Name, version), - Objects: []string{ - getCSVJson(pkg.Name, version), - getCRDJSON(), - }, - Version: semver.MustParse(version), - } -} - func getCSVJson(pkgName, version string) string { return fmt.Sprintf(`{"kind": "ClusterServiceVersion", "apiVersion": "operators.coreos.com/v1alpha1", "metadata":{"name":%q}}`, testBundleName(pkgName, version)) } @@ -257,66 +219,6 @@ func getCRDJSON() string { return `{"kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1"}` } -func buildAnakinPkgModel() *model.Package { - pkgName := "anakin" - - pkg := &model.Package{ - Name: pkgName, - Description: testPackageDescription(pkgName), - Icon: &model.Icon{ - Data: []byte(svgSmallCircle), - MediaType: "image/svg+xml", - }, - Channels: map[string]*model.Channel{}, - } - - light := &model.Channel{ - Package: pkg, - Name: "light", - Bundles: map[string]*model.Bundle{}, - } - - dark := &model.Channel{ - Package: pkg, - Name: "dark", - Bundles: map[string]*model.Bundle{}, - } - light.Bundles[testBundleName(pkgName, "0.0.1")] = getBundle(pkg, light, "0.0.1", "") - light.Bundles[testBundleName(pkgName, "0.1.0")] = getBundle(pkg, light, "0.1.0", testBundleName(pkgName, "0.0.1")) - - dark.Bundles[testBundleName(pkgName, "0.0.1")] = getBundle(pkg, dark, "0.0.1", "") - dark.Bundles[testBundleName(pkgName, "0.1.0")] = getBundle(pkg, dark, "0.1.0", testBundleName(pkgName, "0.0.1")) - dark.Bundles[testBundleName(pkgName, "0.1.1")] = getBundle(pkg, dark, "0.1.1", testBundleName(pkgName, "0.0.1"), testBundleName(pkgName, "0.1.0")) - - pkg.Channels["light"] = light - pkg.Channels["dark"] = dark - pkg.DefaultChannel = pkg.Channels["dark"] - return pkg -} - -func buildBobaFettPkgModel() *model.Package { - pkgName := "boba-fett" - pkg := &model.Package{ - Name: pkgName, - Description: testPackageDescription(pkgName), - Icon: &model.Icon{ - Data: []byte(svgBigCircle), - MediaType: "image/svg+xml", - }, - Channels: map[string]*model.Channel{}, - } - mando := &model.Channel{ - Package: pkg, - Name: "mando", - Bundles: map[string]*model.Bundle{}, - } - mando.Bundles[testBundleName(pkgName, "1.0.0")] = getBundle(pkg, mando, "1.0.0", "") - mando.Bundles[testBundleName(pkgName, "2.0.0")] = getBundle(pkg, mando, "2.0.0", testBundleName(pkgName, "1.0.0")) - pkg.Channels["mando"] = mando - pkg.DefaultChannel = mando - return pkg -} - func testPackageDescription(pkg string) string { return fmt.Sprintf("%s operator", pkg) } diff --git a/alpha/declcfg/image_validation.go b/alpha/declcfg/image_validation.go new file mode 100644 index 000000000..e4d86e742 --- /dev/null +++ b/alpha/declcfg/image_validation.go @@ -0,0 +1,19 @@ +package declcfg + +import ( + "fmt" + + "go.podman.io/image/v5/docker/reference" +) + +// validateImagePullSpec checks that a non-empty image pull spec is valid +// Empty pull specs are not validated. +func validateImagePullSpec(pullSpec, errFormat string, errArgs ...interface{}) error { + if pullSpec == "" { + return nil + } + if _, err := reference.ParseNormalizedNamed(pullSpec); err != nil { + return fmt.Errorf(errFormat+": invalid image pull spec %q: %w", append(errArgs, pullSpec, err)...) + } + return nil +} diff --git a/alpha/declcfg/model_to_declcfg.go b/alpha/declcfg/model_to_declcfg.go deleted file mode 100644 index a7732581e..000000000 --- a/alpha/declcfg/model_to_declcfg.go +++ /dev/null @@ -1,133 +0,0 @@ -package declcfg - -import ( - "sort" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func ConvertFromModel(mpkgs model.Model) DeclarativeConfig { - cfg := DeclarativeConfig{} - for _, mpkg := range mpkgs { - channels, bundles := traverseModelChannels(*mpkg) - - var i *Icon - if mpkg.Icon != nil { - i = &Icon{ - Data: mpkg.Icon.Data, - MediaType: mpkg.Icon.MediaType, - } - } - defaultChannel := "" - if mpkg.DefaultChannel != nil { - defaultChannel = mpkg.DefaultChannel.Name - } - cfg.Packages = append(cfg.Packages, Package{ - Schema: SchemaPackage, - Name: mpkg.Name, - DefaultChannel: defaultChannel, - Icon: i, - Description: mpkg.Description, - }) - cfg.Channels = append(cfg.Channels, channels...) - cfg.Bundles = append(cfg.Bundles, bundles...) - } - - sort.Slice(cfg.Packages, func(i, j int) bool { - return cfg.Packages[i].Name < cfg.Packages[j].Name - }) - sort.Slice(cfg.Channels, func(i, j int) bool { - if cfg.Channels[i].Package != cfg.Channels[j].Package { - return cfg.Channels[i].Package < cfg.Channels[j].Package - } - return cfg.Channels[i].Name < cfg.Channels[j].Name - }) - sort.Slice(cfg.Bundles, func(i, j int) bool { - if cfg.Bundles[i].Package != cfg.Bundles[j].Package { - return cfg.Bundles[i].Package < cfg.Bundles[j].Package - } - return cfg.Bundles[i].Name < cfg.Bundles[j].Name - }) - - return cfg -} - -func traverseModelChannels(mpkg model.Package) ([]Channel, []Bundle) { - channels := make([]Channel, 0, len(mpkg.Channels)) - bundleMap := map[string]*Bundle{} - - for _, ch := range mpkg.Channels { - // initialize channel - c := Channel{ - Schema: SchemaChannel, - Name: ch.Name, - Package: ch.Package.Name, - Entries: []ChannelEntry{}, - // NOTICE: The field Properties of the type Channel is 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. - Properties: ch.Properties, - } - - for _, chb := range ch.Bundles { - // populate channel entry - c.Entries = append(c.Entries, ChannelEntry{ - Name: chb.Name, - Replaces: chb.Replaces, - Skips: chb.Skips, - SkipRange: chb.SkipRange, - }) - - // create or update bundle - b, ok := bundleMap[chb.Name] - if !ok { - b = &Bundle{ - Schema: SchemaBundle, - Name: chb.Name, - Package: chb.Package.Name, - Image: chb.Image, - RelatedImages: ModelRelatedImagesToRelatedImages(chb.RelatedImages), - CsvJSON: chb.CsvJSON, - Objects: chb.Objects, - } - bundleMap[b.Name] = b - } - b.Properties = append(b.Properties, chb.Properties...) - } - - // sort channel entries by name - sort.Slice(c.Entries, func(i, j int) bool { - return c.Entries[i].Name < c.Entries[j].Name - }) - channels = append(channels, c) - } - - // nolint:prealloc - var bundles []Bundle - for _, b := range bundleMap { - b.Properties = property.Deduplicate(b.Properties) - - sort.Slice(b.Properties, func(i, j int) bool { - if b.Properties[i].Type != b.Properties[j].Type { - return b.Properties[i].Type < b.Properties[j].Type - } - return string(b.Properties[i].Value) < string(b.Properties[j].Value) - }) - - bundles = append(bundles, *b) - } - return channels, bundles -} - -func ModelRelatedImagesToRelatedImages(relatedImages []model.RelatedImage) []RelatedImage { - // nolint:prealloc - var out []RelatedImage - for _, ri := range relatedImages { - out = append(out, RelatedImage{ - Name: ri.Name, - Image: ri.Image, - }) - } - return out -} diff --git a/alpha/declcfg/model_to_declcfg_test.go b/alpha/declcfg/model_to_declcfg_test.go deleted file mode 100644 index 07fe7d577..000000000 --- a/alpha/declcfg/model_to_declcfg_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package declcfg - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/alpha/model" -) - -func TestConvertFromModel(t *testing.T) { - type spec struct { - name string - m model.Model - expectCfg DeclarativeConfig - } - - specs := []spec{ - { - name: "Success", - m: buildTestModel(), - expectCfg: buildValidDeclarativeConfig(validDeclarativeConfigSpec{IncludeUnrecognized: false, IncludeDeprecations: false}), - }, - } - - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - s.m.Normalize() - require.NoError(t, s.m.Validate()) - actual := ConvertFromModel(s.m) - - removeJSONWhitespace(&s.expectCfg) - removeJSONWhitespace(&actual) - - assert.Equal(t, s.expectCfg, actual) - }) - } -} diff --git a/alpha/declcfg/validate.go b/alpha/declcfg/validate.go new file mode 100644 index 000000000..0e56c3c0b --- /dev/null +++ b/alpha/declcfg/validate.go @@ -0,0 +1,206 @@ +package declcfg + +import ( + "fmt" + + "github.com/blang/semver/v4" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + + "github.com/operator-framework/operator-registry/alpha/property" +) + +// Validate validates a DeclarativeConfig without converting to model. +// It performs the same validation checks as ConvertToModel but doesn't build the model structure. +func Validate(cfg DeclarativeConfig) error { + packageNames := sets.New[string]() + defaultChannels := map[string]string{} + + // Validate packages + for _, p := range cfg.Packages { + if p.Name == "" { + return fmt.Errorf("config contains package with no name") + } + + if packageNames.Has(p.Name) { + return fmt.Errorf("duplicate package %q", p.Name) + } + packageNames.Insert(p.Name) + + if errs := validation.IsDNS1123Label(p.Name); len(errs) > 0 { + return fmt.Errorf("invalid package name %q: %v", p.Name, errs) + } + + defaultChannels[p.Name] = p.DefaultChannel + } + + // Validate channels + packageChannels := make(map[string]sets.Set[string]) + channelDefinedEntries := map[string]sets.Set[string]{} + for _, c := range cfg.Channels { + if !packageNames.Has(c.Package) { + return fmt.Errorf("unknown package %q for channel %q", c.Package, c.Name) + } + + if c.Name == "" { + return fmt.Errorf("package %q contains channel with no name", c.Package) + } + + if _, ok := packageChannels[c.Package]; !ok { + packageChannels[c.Package] = sets.New[string]() + } + if packageChannels[c.Package].Has(c.Name) { + return fmt.Errorf("package %q has duplicate channel %q", c.Package, c.Name) + } + packageChannels[c.Package].Insert(c.Name) + + // Track entries defined in channel + cde := sets.Set[string]{} + seenEntries := sets.New[string]() + for _, entry := range c.Entries { + if seenEntries.Has(entry.Name) { + return fmt.Errorf("invalid package %q, channel %q: duplicate entry %q", c.Package, c.Name, entry.Name) + } + seenEntries.Insert(entry.Name) + cde = cde.Insert(entry.Name) + } + channelDefinedEntries[c.Package] = cde + } + + // Validate bundles + packageBundles := map[string]sets.Set[string]{} + for _, b := range cfg.Bundles { + if b.Package == "" { + return fmt.Errorf("package name must be set for bundle %q", b.Name) + } + if !packageNames.Has(b.Package) { + return fmt.Errorf("unknown package %q for bundle %q", b.Package, b.Name) + } + + bundles, ok := packageBundles[b.Package] + if !ok { + bundles = sets.Set[string]{} + } + if bundles.Has(b.Name) { + return fmt.Errorf("package %q has duplicate bundle %q", b.Package, b.Name) + } + bundles.Insert(b.Name) + packageBundles[b.Package] = bundles + + props, err := property.Parse(b.Properties) + if err != nil { + return fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) + } + + if len(props.Packages) != 1 { + return fmt.Errorf("package %q bundle %q must have exactly 1 %q property, found %d", b.Package, b.Name, property.TypePackage, len(props.Packages)) + } + + if b.Package != props.Packages[0].PackageName { + return fmt.Errorf("package %q does not match %q property %q", b.Package, property.TypePackage, props.Packages[0].PackageName) + } + + if err := validateImagePullSpec(b.Image, "package %q bundle %q image", b.Package, b.Name); err != nil { + return err + } + for i, rel := range b.RelatedImages { + if err := validateImagePullSpec(rel.Image, "package %q bundle %q relatedImages[%d].image", b.Package, b.Name, i); err != nil { + return err + } + } + + // Validate version + rawVersion := props.Packages[0].Version + if _, err := semver.Parse(rawVersion); err != nil { + return fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err) + } + + channelDefinedEntries[b.Package] = channelDefinedEntries[b.Package].Delete(b.Name) + + // Check that bundle is in at least one channel + found := false + for _, ch := range cfg.Channels { + if ch.Package != b.Package { + continue + } + for _, entry := range ch.Entries { + if entry.Name == b.Name { + found = true + break + } + } + if found { + break + } + } + if !found { + return fmt.Errorf("package %q, bundle %q not found in any channel entries", b.Package, b.Name) + } + } + + // Check for channel entries without bundles + for pkg, entries := range channelDefinedEntries { + if entries.Len() > 0 { + return fmt.Errorf("no olm.bundle blobs found in package %q for olm.channel entries %s", pkg, sets.List[string](entries)) + } + } + + // Validate default channels exist + for pkg, defaultChannel := range defaultChannels { + if defaultChannel != "" && !packageChannels[pkg].Has(defaultChannel) { + return fmt.Errorf("package %q references non-existent default channel %q", pkg, defaultChannel) + } + } + + // Validate deprecations + deprecationsByPackage := sets.New[string]() + for i, deprecation := range cfg.Deprecations { + if deprecation.Package == "" { + return fmt.Errorf("package name must be set for deprecation item %v", i) + } + + if !packageNames.Has(deprecation.Package) { + return fmt.Errorf("cannot apply deprecations to an unknown package %q", deprecation.Package) + } + + if deprecationsByPackage.Has(deprecation.Package) { + return fmt.Errorf("expected a maximum of one deprecation per package: %q", deprecation.Package) + } + deprecationsByPackage.Insert(deprecation.Package) + + references := sets.New[PackageScopedReference]() + for j, entry := range deprecation.Entries { + if entry.Reference.Schema == "" { + return fmt.Errorf("schema must be set for deprecation entry [%v] for package %q", j, deprecation.Package) + } + + if references.Has(entry.Reference) { + return fmt.Errorf("duplicate deprecation entry %#v for package %q", entry.Reference, deprecation.Package) + } + references.Insert(entry.Reference) + + switch entry.Reference.Schema { + case SchemaBundle: + if !packageBundles[deprecation.Package].Has(entry.Reference.Name) { + return fmt.Errorf("cannot deprecate bundle %q for package %q: bundle not found", entry.Reference.Name, deprecation.Package) + } + case SchemaChannel: + if !packageChannels[deprecation.Package].Has(entry.Reference.Name) { + return fmt.Errorf("cannot deprecate channel %q for package %q: channel not found", entry.Reference.Name, deprecation.Package) + } + case SchemaPackage: + if entry.Reference.Name != "" { + return fmt.Errorf("package name must be empty for deprecated package %q (specified %q)", deprecation.Package, entry.Reference.Name) + } + default: + return fmt.Errorf("cannot deprecate object %#v referenced by entry %v for package %q: object schema unknown", entry.Reference, j, deprecation.Package) + } + } + } + + // TODO: Validate channel graphs (no circular replaces, valid heads, etc.) + // This would require building the graph structure, which is what ConvertToModel does. + // For now, this validation is "good enough" and catches most common errors. + + return nil +} diff --git a/alpha/model/versionrelease.go b/alpha/declcfg/versionrelease.go similarity index 99% rename from alpha/model/versionrelease.go rename to alpha/declcfg/versionrelease.go index 0c295f99d..ca6008d01 100644 --- a/alpha/model/versionrelease.go +++ b/alpha/declcfg/versionrelease.go @@ -1,4 +1,4 @@ -package model +package declcfg import ( "encoding/json" diff --git a/alpha/model/error.go b/alpha/model/error.go deleted file mode 100644 index e175b3d40..000000000 --- a/alpha/model/error.go +++ /dev/null @@ -1,69 +0,0 @@ -package model - -import ( - "bytes" - "errors" - "fmt" - "strings" -) - -type validationError struct { - message string - subErrors []error -} - -func newValidationError(message string) *validationError { - return &validationError{message: message} -} - -func (v *validationError) orNil() error { - if len(v.subErrors) == 0 { - return nil - } - return v -} - -func (v *validationError) Error() string { - if v == nil { - return "" - } - return strings.TrimSpace(v.errorPrefix(nil, true, nil)) -} - -func (v *validationError) errorPrefix(prefix []rune, last bool, seen []error) string { - for _, s := range seen { - if errors.Is(v, s) { - return "" - } - } - seen = append(seen, v) - sep := ":\n" - if len(v.subErrors) == 0 { - sep = "\n" - } - errMsg := bytes.NewBufferString(fmt.Sprintf("%s%s%s", string(prefix), v.message, sep)) - for i, serr := range v.subErrors { - subPrefix := prefix - if len(subPrefix) >= 4 { - if last { - subPrefix = append(subPrefix[0:len(subPrefix)-4], []rune(" ")...) - } else { - subPrefix = append(subPrefix[0:len(subPrefix)-4], []rune("│ ")...) - } - } - subLast := i == len(v.subErrors)-1 - if subLast { - subPrefix = append(subPrefix, []rune("└── ")...) - } else { - subPrefix = append(subPrefix, []rune("├── ")...) - } - - var verr *validationError - if errors.As(serr, &verr) { - errMsg.WriteString(verr.errorPrefix(subPrefix, subLast, seen)) - } else { - fmt.Fprintf(errMsg, "%s%s\n", string(subPrefix), serr) - } - } - return errMsg.String() -} diff --git a/alpha/model/error_test.go b/alpha/model/error_test.go deleted file mode 100644 index 3f53aefd0..000000000 --- a/alpha/model/error_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package model - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestValidationError_Error(t *testing.T) { - type spec struct { - name string - err *validationError - expect string - } - - recursiveErr := &validationError{ - message: "l1", - } - recursiveErr.subErrors = []error{ - fmt.Errorf("err1"), - &validationError{ - message: "l2", - subErrors: []error{ - fmt.Errorf("err3"), - recursiveErr, - fmt.Errorf("err4"), - }, - }, - fmt.Errorf("err2"), - } - - specs := []spec{ - { - name: "Nil", - err: nil, - expect: "", - }, - { - name: "Empty", - err: &validationError{}, - expect: "", - }, - { - name: "RecursiveError", - err: recursiveErr, - expect: `l1: -├── err1 -├── l2: -│ ├── err3 -│ └── err4 -└── err2`, - }, - { - name: "MessageOnly", - err: &validationError{message: "hello"}, - expect: "hello", - }, - { - name: "WithSubErrors", - err: &validationError{ - message: "hello", - subErrors: []error{ - fmt.Errorf("world"), - fmt.Errorf("foobar"), - }}, - expect: `hello: -├── world -└── foobar`, - }, - { - name: "WithEmptyLeafSubErrors", - err: &validationError{ - message: "hello", - subErrors: []error{ - &validationError{ - message: "foo", - subErrors: []error{}, - }, - &validationError{ - message: "bar", - subErrors: []error{ - fmt.Errorf("bar1"), - fmt.Errorf("bar2"), - }, - }, - }}, - expect: `hello: -├── foo -└── bar: - ├── bar1 - └── bar2`, - }, - { - name: "WithSubSubErrors", - err: &validationError{ - message: "hello", - subErrors: []error{ - &validationError{ - message: "foo", - subErrors: []error{ - fmt.Errorf("foo1"), - fmt.Errorf("foo2"), - }, - }, - &validationError{ - message: "bar", - subErrors: []error{ - fmt.Errorf("bar1"), - fmt.Errorf("bar2"), - }, - }, - }}, - expect: `hello: -├── foo: -│ ├── foo1 -│ └── foo2 -└── bar: - ├── bar1 - └── bar2`, - }, - } - - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - require.Equal(t, s.expect, s.err.Error()) - }) - } -} diff --git a/alpha/model/model.go b/alpha/model/model.go deleted file mode 100644 index 1b4e8aff6..000000000 --- a/alpha/model/model.go +++ /dev/null @@ -1,498 +0,0 @@ -package model - -import ( - "errors" - "fmt" - "slices" - "sort" - "strings" - - "github.com/blang/semver/v4" - "github.com/h2non/filetype" - "github.com/h2non/filetype/matchers" - "github.com/h2non/filetype/types" - svg "github.com/h2non/go-is-svg" - "golang.org/x/exp/maps" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/operator-framework/operator-registry/alpha/property" -) - -type Deprecation struct { - Message string `json:"message"` -} - -func init() { - t := types.NewType("svg", "image/svg+xml") - filetype.AddMatcher(t, svg.Is) - matchers.Image[types.NewType("svg", "image/svg+xml")] = svg.Is -} - -type Model map[string]*Package - -func (m Model) Validate() error { - result := newValidationError("invalid index") - - for name, pkg := range m { - if name != pkg.Name { - result.subErrors = append(result.subErrors, fmt.Errorf("package key %q does not match package name %q", name, pkg.Name)) - } - if err := pkg.Validate(); err != nil { - result.subErrors = append(result.subErrors, err) - } - } - return result.orNil() -} - -type Package struct { - Name string - Description string - Icon *Icon - DefaultChannel *Channel - Channels map[string]*Channel - Deprecation *Deprecation -} - -func (p *Package) Validate() error { - result := newValidationError(fmt.Sprintf("invalid package %q", p.Name)) - - if p.Name == "" { - result.subErrors = append(result.subErrors, errors.New("package name must not be empty")) - } - - if err := p.Icon.Validate(); err != nil { - result.subErrors = append(result.subErrors, err) - } - - if p.DefaultChannel == nil { - result.subErrors = append(result.subErrors, fmt.Errorf("default channel must be set")) - } - - if len(p.Channels) == 0 { - result.subErrors = append(result.subErrors, fmt.Errorf("package must contain at least one channel")) - } - - foundDefault := false - for name, ch := range p.Channels { - if name != ch.Name { - result.subErrors = append(result.subErrors, fmt.Errorf("channel key %q does not match channel name %q", name, ch.Name)) - } - if err := ch.Validate(); err != nil { - result.subErrors = append(result.subErrors, err) - } - if ch == p.DefaultChannel { - foundDefault = true - } - if ch.Package != p { - result.subErrors = append(result.subErrors, fmt.Errorf("channel %q not correctly linked to parent package", ch.Name)) - } - } - - if err := p.validateUniqueBundleVersions(); err != nil { - result.subErrors = append(result.subErrors, err) - } - - if p.DefaultChannel != nil && !foundDefault { - result.subErrors = append(result.subErrors, fmt.Errorf("default channel %q not found in channels list", p.DefaultChannel.Name)) - } - - if err := p.Deprecation.Validate(); err != nil { - result.subErrors = append(result.subErrors, fmt.Errorf("invalid deprecation: %v", err)) - } - - return result.orNil() -} - -func (p *Package) validateUniqueBundleVersions() error { - versionsMap := map[string]string{} - bundlesWithVersion := map[string]sets.Set[string]{} - for _, ch := range p.Channels { - for _, b := range ch.Bundles { - versionsMap[b.VersionString()] = b.VersionString() - if bundlesWithVersion[b.VersionString()] == nil { - bundlesWithVersion[b.VersionString()] = sets.New[string]() - } - bundlesWithVersion[b.VersionString()].Insert(b.Name) - } - } - - versionsSlice := maps.Values(versionsMap) - slices.Sort(versionsSlice) - - var errs []error - for _, v := range versionsSlice { - bundles := sets.List(bundlesWithVersion[v]) - if len(bundles) > 1 { - errs = append(errs, fmt.Errorf("{%s: [%s]}", v, strings.Join(bundles, ", "))) - } - } - - if len(errs) > 0 { - return fmt.Errorf("duplicate versions found in bundles: %v", errs) - } - return nil -} - -type Icon struct { - Data []byte `json:"base64data"` - MediaType string `json:"mediatype"` -} - -func (i *Icon) Validate() error { - if i == nil { - return nil - } - // TODO(joelanford): Should we check that data and mediatype are set, - // and detect the media type of the data and compare it to the - // mediatype listed in the icon field? Currently, some production - // index databases are failing these tests, so leaving this - // commented out for now. - result := newValidationError("invalid icon") - //if len(i.Data) == 0 { - // result.subErrors = append(result.subErrors, errors.New("icon data must be set if icon is defined")) - //} - //if len(i.MediaType) == 0 { - // result.subErrors = append(result.subErrors, errors.New("icon mediatype must be set if icon is defined")) - //} - //if len(i.Data) > 0 { - // if err := i.validateData(); err != nil { - // result.subErrors = append(result.subErrors, err) - // } - //} - return result.orNil() -} - -// nolint:unused -func (i *Icon) validateData() error { - if !filetype.IsImage(i.Data) { - return errors.New("icon data is not an image") - } - t, err := filetype.Match(i.Data) - if err != nil { - return err - } - if t.MIME.Value != i.MediaType { - return fmt.Errorf("icon media type %q does not match detected media type %q", i.MediaType, t.MIME.Value) - } - return nil -} - -type Channel struct { - Package *Package - Name string - Bundles map[string]*Bundle - Deprecation *Deprecation - // NOTICE: The field Properties of the type Channel is 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. - Properties []property.Property -} - -// TODO(joelanford): This function determines the channel head by finding the bundle that has 0 -// -// incoming edges, based on replaces and skips. It also expects to find exactly one such bundle. -// Is this the correct algorithm? -func (c Channel) Head() (*Bundle, error) { - incoming := map[string]int{} - for _, b := range c.Bundles { - if b.Replaces != "" { - incoming[b.Replaces]++ - } - for _, skip := range b.Skips { - incoming[skip]++ - } - } - var heads []*Bundle - for _, b := range c.Bundles { - if _, ok := incoming[b.Name]; !ok { - heads = append(heads, b) - } - } - if len(heads) == 0 { - return nil, fmt.Errorf("no channel head found in graph") - } - if len(heads) > 1 { - var headNames []string - for _, head := range heads { - headNames = append(headNames, head.Name) - } - sort.Strings(headNames) - return nil, fmt.Errorf("multiple channel heads found in graph: %s", strings.Join(headNames, ", ")) - } - return heads[0], nil -} - -func (c *Channel) Validate() error { - result := newValidationError(fmt.Sprintf("invalid channel %q", c.Name)) - - if c.Name == "" { - result.subErrors = append(result.subErrors, errors.New("channel name must not be empty")) - } - - if c.Package == nil { - result.subErrors = append(result.subErrors, errors.New("package must be set")) - } - - if len(c.Bundles) == 0 { - result.subErrors = append(result.subErrors, fmt.Errorf("channel must contain at least one bundle")) - } - - if len(c.Bundles) > 0 { - if err := c.validateReplacesChain(); err != nil { - result.subErrors = append(result.subErrors, err) - } - } - - for name, b := range c.Bundles { - if name != b.Name { - result.subErrors = append(result.subErrors, fmt.Errorf("bundle key %q does not match bundle name %q", name, b.Name)) - } - if err := b.Validate(); err != nil { - result.subErrors = append(result.subErrors, err) - } - if b.Channel != c { - result.subErrors = append(result.subErrors, fmt.Errorf("bundle %q not correctly linked to parent channel", b.Name)) - } - } - - if err := c.Deprecation.Validate(); err != nil { - result.subErrors = append(result.subErrors, fmt.Errorf("invalid deprecation: %v", err)) - } - - return result.orNil() -} - -// validateReplacesChain checks the replaces chain of a channel. -// Specifically the following rules must be followed: -// 1. There must be exactly 1 channel head. -// 2. Beginning at the head, the replaces chain must reach all non-skipped entries. -// Non-skipped entries are defined as entries that are not skipped by any other entry in the channel. -// 3. There must be no cycles in the replaces chain. -// 4. The tail entry in the replaces chain is permitted to replace a non-existent entry. -func (c *Channel) validateReplacesChain() error { - head, err := c.Head() - if err != nil { - return err - } - - allBundles := sets.NewString() - skippedBundles := sets.NewString() - for _, b := range c.Bundles { - allBundles = allBundles.Insert(b.Name) - skippedBundles = skippedBundles.Insert(b.Skips...) - } - - chainFrom := map[string][]string{} - replacesChainFromHead := sets.NewString(head.Name) - cur := head - for cur != nil { - if _, ok := chainFrom[cur.Name]; !ok { - chainFrom[cur.Name] = []string{cur.Name} - } - // if the replaces edge is known to be skipped, disregard it - if skippedBundles.Has(cur.Replaces) { - break - } - for k := range chainFrom { - chainFrom[k] = append(chainFrom[k], cur.Replaces) - } - if replacesChainFromHead.Has(cur.Replaces) { - return fmt.Errorf("detected cycle in replaces chain of upgrade graph: %s", strings.Join(chainFrom[cur.Replaces], " -> ")) - } - replacesChainFromHead = replacesChainFromHead.Insert(cur.Replaces) - cur = c.Bundles[cur.Replaces] - } - - strandedBundles := allBundles.Difference(replacesChainFromHead).Difference(skippedBundles).List() - if len(strandedBundles) > 0 { - return fmt.Errorf("channel contains one or more stranded bundles: %s", strings.Join(strandedBundles, ", ")) - } - - return nil -} - -type Bundle struct { - Package *Package - Channel *Channel - Name string - Image string - Replaces string - Skips []string - SkipRange string - Properties []property.Property - RelatedImages []RelatedImage - Deprecation *Deprecation - - // These fields are present so that we can continue serving - // the GRPC API the way packageserver expects us to in a - // backwards-compatible way. - Objects []string - CsvJSON string - - // These fields are used to compare bundles in a diff. - PropertiesP *property.Properties - Version semver.Version - Release semver.Version -} - -func (b *Bundle) VersionString() string { - if len(b.Release.Pre) > 0 { - pres := make([]string, 0, len(b.Release.Pre)) - for _, pre := range b.Release.Pre { - pres = append(pres, pre.String()) - } - relString := strings.Join(pres, ".") - return strings.Join([]string{b.Version.String(), relString}, "-") - } - return b.Version.String() -} - -func (b *Bundle) normalizeName() string { - // if the bundle has release versioning, then the name must include this in standard form: - // -v- - // if no release versioning exists, then just return the bundle name - if len(b.Release.Pre) > 0 { - return strings.Join([]string{b.Package.Name, "v" + b.VersionString()}, "-") - } - return b.Name -} - -// order by version, then -// release, if present -func (b *Bundle) Compare(other *Bundle) int { - if b.Name == other.Name { - return 0 - } - if b.Version.NE(other.Version) { - return b.Version.Compare(other.Version) - } - bhasrelease := len(b.Release.Pre) > 0 - otherhasrelease := len(other.Release.Pre) > 0 - if bhasrelease && !otherhasrelease { - return 1 - } - if !bhasrelease && otherhasrelease { - return -1 - } - return b.Release.Compare(other.Release) -} - -func (b *Bundle) Validate() error { - result := newValidationError(fmt.Sprintf("invalid bundle %q", b.Name)) - - if b.Name == "" { - result.subErrors = append(result.subErrors, errors.New("name must be set")) - } - if b.Name != b.normalizeName() { - result.subErrors = append(result.subErrors, fmt.Errorf("name %q does not match normalized name %q", b.Name, b.normalizeName())) - } - if b.Channel == nil { - result.subErrors = append(result.subErrors, errors.New("channel must be set")) - } - if b.Package == nil { - result.subErrors = append(result.subErrors, errors.New("package must be set")) - } - if b.Channel != nil && b.Package != nil && b.Package != b.Channel.Package { - result.subErrors = append(result.subErrors, errors.New("package does not match channel's package")) - } - props, err := property.Parse(b.Properties) - if err != nil { - result.subErrors = append(result.subErrors, err) - } - for i, skip := range b.Skips { - if skip == "" { - result.subErrors = append(result.subErrors, fmt.Errorf("skip[%d] is empty", i)) - } - } - // TODO(joelanford): Validate related images? It looks like some - // CSVs in production databases use incorrect fields ([name,value] - // instead of [name,image]), which results in empty image values. - // Example is in redhat-operators: 3scale-operator.v0.5.5 - //for i, relatedImage := range b.RelatedImages { - // if err := relatedImage.Validate(); err != nil { - // result.subErrors = append(result.subErrors, WithIndex(i, err)) - // } - //} - - if props != nil && len(props.Packages) != 1 { - result.subErrors = append(result.subErrors, fmt.Errorf("must be exactly one property with type %q", property.TypePackage)) - } - - if b.Image == "" && len(b.Objects) == 0 { - result.subErrors = append(result.subErrors, errors.New("bundle image must be set")) - } - - if err := b.Deprecation.Validate(); err != nil { - result.subErrors = append(result.subErrors, fmt.Errorf("invalid deprecation: %v", err)) - } - - if len(b.Version.Build) > 0 && len(b.Release.Pre) > 0 { - result.subErrors = append(result.subErrors, fmt.Errorf("cannot use build metadata in version with a release version")) - } - - return result.orNil() -} - -type RelatedImage struct { - Name string - Image string -} - -func (i RelatedImage) Validate() error { - result := newValidationError("invalid related image") - if i.Image == "" { - result.subErrors = append(result.subErrors, fmt.Errorf("image must be set")) - } - return result.orNil() -} - -func (m Model) Normalize() { - for _, pkg := range m { - for _, ch := range pkg.Channels { - for _, b := range ch.Bundles { - for i := range b.Properties { - // Ensure property value is encoded in a standard way. - if normalized, err := property.Build(&b.Properties[i]); err == nil { - b.Properties[i] = *normalized - } - } - } - } - } -} - -func (m Model) AddBundle(b Bundle) { - if _, present := m[b.Package.Name]; !present { - m[b.Package.Name] = b.Package - } - p := m[b.Package.Name] - b.Package = p - - if ch, ok := p.Channels[b.Channel.Name]; ok { - b.Channel = ch - ch.Bundles[b.Name] = &b - } else { - newCh := &Channel{ - Name: b.Channel.Name, - Package: p, - Bundles: make(map[string]*Bundle), - } - b.Channel = newCh - newCh.Bundles[b.Name] = &b - p.Channels[newCh.Name] = newCh - } - - if p.DefaultChannel == nil { - p.DefaultChannel = b.Channel - } -} - -func (d *Deprecation) Validate() error { - if d == nil { - return nil - } - if d.Message == "" { - return errors.New("message must be set") - } - return nil -} diff --git a/alpha/model/model_test.go b/alpha/model/model_test.go deleted file mode 100644 index 898ec3c49..000000000 --- a/alpha/model/model_test.go +++ /dev/null @@ -1,807 +0,0 @@ -package model - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "testing" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/alpha/property" -) - -type validator interface { - Validate() error -} - -const svgData = `PHN2ZyB2aWV3Qm94PTAgMCAxMDAgMTAwPjxjaXJjbGUgY3g9MjUgY3k9MjUgcj0yNS8+PC9zdmc+` -const pngData = `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=` -const jpegData = `/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q==` - -func mustBase64Decode(in string) []byte { - out, err := base64.StdEncoding.DecodeString(in) - if err != nil { - panic(err) - } - return out -} - -func TestNormalize(t *testing.T) { - b := &Bundle{} - pkgs := Model{ - "anakin": { - Channels: map[string]*Channel{ - "alpha": { - Bundles: map[string]*Bundle{ - "anakin.v0.0.1": b, - }, - }, - }, - }, - } - t.Run("Success/IgnoreInvalid", func(t *testing.T) { - invalidJSON := json.RawMessage(`}`) - b.Properties = []property.Property{{Value: invalidJSON}} - pkgs.Normalize() - assert.Equal(t, invalidJSON, b.Properties[0].Value) - }) - - t.Run("Success/Unchanged", func(t *testing.T) { - unchanged := json.RawMessage(`{}`) - b.Properties = []property.Property{{Value: unchanged}} - pkgs.Normalize() - assert.Equal(t, unchanged, b.Properties[0].Value) - }) - - t.Run("Success/RemoveSpaces", func(t *testing.T) { - withWhitespace := json.RawMessage(` { - "foo": "bar" - - } `) - expected := json.RawMessage(`{"foo":"bar"}`) - b.Properties = []property.Property{{Value: withWhitespace}} - pkgs.Normalize() - assert.Equal(t, expected, b.Properties[0].Value) - }) -} - -func TestChannelHead(t *testing.T) { - type spec struct { - name string - ch Channel - head *Bundle - assertion require.ErrorAssertionFunc - } - - head := &Bundle{ - Name: "anakin.v0.0.3", - Replaces: "anakin.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - } - - specs := []spec{ - { - name: "Success/Valid", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2"}, - "anakin.v0.0.3": head, - }}, - head: head, - assertion: require.NoError, - }, - { - name: "Error/NoChannelHead", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1", Replaces: "anakin.v0.0.3"}, - "anakin.v0.0.3": head, - }}, - assertion: hasError(`no channel head found in graph`), - }, - { - name: "Error/MultipleChannelHeads", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - "anakin.v0.0.3": head, - "anakin.v0.0.4": {Name: "anakin.v0.0.4", Replaces: "anakin.v0.0.1"}, - }}, - assertion: hasError(`multiple channel heads found in graph: anakin.v0.0.3, anakin.v0.0.4`), - }, - } - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - h, err := s.ch.Head() - assert.Equal(t, s.head, h) - s.assertion(t, err) - }) - } -} - -func TestValidReplacesChain(t *testing.T) { - type spec struct { - name string - ch Channel - assertion require.ErrorAssertionFunc - } - specs := []spec{ - { - name: "Success/Valid", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Skips: []string{"anakin.v0.0.1"}}, - "anakin.v0.0.3": {Name: "anakin.v0.0.3", Skips: []string{"anakin.v0.0.2"}}, - "anakin.v0.0.4": {Name: "anakin.v0.0.4", Replaces: "anakin.v0.0.3"}, - }}, - assertion: require.NoError, - }, - { - name: "Error/CycleNoHops", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.4": {Name: "anakin.v0.0.4", Replaces: "anakin.v0.0.4"}, - "anakin.v0.0.5": {Name: "anakin.v0.0.5", Replaces: "anakin.v0.0.4"}, - }}, - assertion: hasError(`detected cycle in replaces chain of upgrade graph: anakin.v0.0.4 -> anakin.v0.0.4`), - }, - { - name: "Error/CycleMultipleHops", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1", Replaces: "anakin.v0.0.3"}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Replaces: "anakin.v0.0.1"}, - "anakin.v0.0.3": {Name: "anakin.v0.0.3", Replaces: "anakin.v0.0.2"}, - "anakin.v0.0.4": {Name: "anakin.v0.0.4", Replaces: "anakin.v0.0.3"}, - }}, - assertion: hasError(`detected cycle in replaces chain of upgrade graph: anakin.v0.0.3 -> anakin.v0.0.2 -> anakin.v0.0.1 -> anakin.v0.0.3`), - }, - { - name: "Error/Stranded", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Replaces: "anakin.v0.0.1"}, - "anakin.v0.0.3": {Name: "anakin.v0.0.3", Skips: []string{"anakin.v0.0.2"}}, - }}, - assertion: hasError(`channel contains one or more stranded bundles: anakin.v0.0.1`), - }, - { - name: "Error/SkippedReplacesStranded", - ch: Channel{Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Replaces: "anakin.v0.0.1"}, - "anakin.v0.0.3": {Name: "anakin.v0.0.3", Replaces: "anakin.v0.0.2", Skips: []string{"anakin.v0.0.2"}}, - }}, - assertion: hasError(`channel contains one or more stranded bundles: anakin.v0.0.1`), - }} - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - err := s.ch.validateReplacesChain() - s.assertion(t, err) - }) - } -} - -func hasError(expectedError string) require.ErrorAssertionFunc { - return func(t require.TestingT, actualError error, args ...interface{}) { - if stdt, ok := t.(*testing.T); ok { - stdt.Helper() - } - errsToCheck := []error{actualError} - for len(errsToCheck) > 0 { - var err error - err, errsToCheck = errsToCheck[0], errsToCheck[1:] - if err == nil { - continue - } - var verr *validationError - if errors.As(err, &verr) { - if verr.message == expectedError { - return - } - errsToCheck = append(errsToCheck, verr.subErrors...) - } else if expectedError == err.Error() { - return - } - } - t.Errorf("expected error to be or contain suberror `%s`, got `%s`", expectedError, actualError) - t.FailNow() - } -} - -func TestValidators(t *testing.T) { - type spec struct { - name string - v validator - assertion require.ErrorAssertionFunc - } - - pkg, ch := makePackageChannelBundle() - pkgIncorrectDefaultChannel, _ := makePackageChannelBundle() - pkgIncorrectDefaultChannel.DefaultChannel = &Channel{Name: "not-found"} - - var nilIcon *Icon = nil - - specs := []spec{ - { - name: "Model/Success/Valid", - v: Model{ - pkg.Name: pkg, - }, - assertion: require.NoError, - }, - { - name: "Model/Error/PackageKeyNameMismatch", - v: Model{ - "foo": pkg, - }, - assertion: hasError(`package key "foo" does not match package name "anakin"`), - }, - { - name: "Model/Error/InvalidPackage", - v: Model{ - pkgIncorrectDefaultChannel.Name: pkgIncorrectDefaultChannel, - }, - assertion: hasError(`invalid package "anakin"`), - }, - { - name: "Package/Success/Valid", - v: pkg, - assertion: require.NoError, - }, - { - name: "Package/Error/NoName", - v: &Package{}, - assertion: hasError("package name must not be empty"), - }, - //{ - // name: "Package/Error/InvalidIcon", - // v: &Package{ - // Name: "anakin", - // Icon: &Icon{Data: mustBase64Decode(svgData)}, - // }, - // assertion: hasError("icon mediatype must be set if icon is defined"), - //}, - { - name: "Package/Error/NoChannels", - v: &Package{ - Name: "anakin", - Icon: &Icon{Data: mustBase64Decode(svgData), MediaType: "image/svg+xml"}, - }, - assertion: hasError("package must contain at least one channel"), - }, - { - name: "Package/Error/DuplicateBundleVersions", - v: &Package{ - Name: "anakin", - Channels: map[string]*Channel{ - "light": { - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1", Version: semver.MustParse("0.0.1")}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Version: semver.MustParse("0.0.1")}, - "anakin.v1.0.1": {Name: "anakin.v1.0.1", Version: semver.MustParse("1.0.1")}, - "anakin.v1.0.2": {Name: "anakin.v1.0.2", Version: semver.MustParse("1.0.1")}, - }, - }, - }, - }, - assertion: hasError(`duplicate versions found in bundles: [{0.0.1: [anakin.v0.0.1, anakin.v0.0.2]} {1.0.1: [anakin.v1.0.1, anakin.v1.0.2]}]`), - }, - { - name: "Package/Error/DuplicateBundleVersionsReleases", - v: &Package{ - Name: "anakin", - Channels: map[string]*Channel{ - "light": { - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.1": {Name: "anakin.v0.0.1", Version: semver.MustParse("0.0.1")}, - "anakin.v0.0.2": {Name: "anakin.v0.0.2", Version: semver.MustParse("0.0.1")}, - "anakin-v0.0.1-100": {Name: "anakin.v0.0.1", Version: semver.MustParse("0.0.1"), Release: semver.MustParse(fmt.Sprintf("0.0.0-%s", "100")), Package: pkg}, - "anakin-v0.0.2-100": {Name: "anakin.v0.0.2", Version: semver.MustParse("0.0.1"), Release: semver.MustParse(fmt.Sprintf("0.0.0-%s", "100")), Package: pkg}, - }, - }, - }, - }, - assertion: hasError(`duplicate versions found in bundles: [{0.0.1: [anakin.v0.0.1, anakin.v0.0.2]} {0.0.1-100: [anakin.v0.0.1, anakin.v0.0.2]}]`), - }, - { - name: "Package/Error/BundleReleaseNormalizedName", - v: &Package{ - Name: "anakin", - Channels: map[string]*Channel{ - "light": { - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.1.alpha1": {Name: "anakin.v0.0.1.alpha1", Version: semver.MustParse("0.0.1"), Release: semver.MustParse(fmt.Sprintf("0.0.0-%s", "alpha1")), Package: pkg}, - }, - }, - }, - }, - assertion: hasError(`name "anakin.v0.0.1.alpha1" does not match normalized name "anakin-v0.0.1-alpha1"`), - }, - { - name: "Package/Error/NoDefaultChannel", - v: &Package{ - Name: "anakin", - Icon: &Icon{Data: mustBase64Decode(svgData), MediaType: "image/svg+xml"}, - Channels: map[string]*Channel{"light": ch}, - }, - assertion: hasError("default channel must be set"), - }, - { - name: "Package/Error/ChannelKeyNameMismatch", - v: &Package{ - Name: "anakin", - Icon: &Icon{Data: mustBase64Decode(svgData), MediaType: "image/svg+xml"}, - DefaultChannel: ch, - Channels: map[string]*Channel{"dark": ch}, - }, - assertion: hasError(`channel key "dark" does not match channel name "light"`), - }, - { - name: "Package/Error/InvalidChannel", - v: &Package{ - Name: "anakin", - Icon: &Icon{Data: mustBase64Decode(svgData), MediaType: "image/svg+xml"}, - DefaultChannel: ch, - Channels: map[string]*Channel{"light": {Name: "light"}}, - }, - assertion: hasError(`invalid channel "light"`), - }, - { - name: "Package/Error/InvalidChannelPackageLink", - v: &Package{ - Name: "anakin", - Icon: &Icon{Data: mustBase64Decode(svgData), MediaType: "image/svg+xml"}, - DefaultChannel: ch, - Channels: map[string]*Channel{"light": ch}, - }, - assertion: hasError(`channel "light" not correctly linked to parent package`), - }, - { - name: "Package/Error/DefaultChannelNotInChannelMap", - v: pkgIncorrectDefaultChannel, - assertion: hasError(`default channel "not-found" not found in channels list`), - }, - { - name: "Icon/Success/ValidSVG", - v: &Icon{ - Data: mustBase64Decode(svgData), - MediaType: "image/svg+xml", - }, - assertion: require.NoError, - }, - { - name: "Icon/Success/ValidPNG", - v: &Icon{ - Data: mustBase64Decode(pngData), - MediaType: "image/png", - }, - assertion: require.NoError, - }, - { - name: "Icon/Success/ValidJPEG", - v: &Icon{ - Data: mustBase64Decode(jpegData), - MediaType: "image/jpeg", - }, - assertion: require.NoError, - }, - { - name: "Icon/Success/Nil", - v: nilIcon, - assertion: require.NoError, - }, - //{ - // name: "Icon/Error/NoData", - // v: &Icon{ - // Data: nil, - // MediaType: "image/svg+xml", - // }, - // assertion: hasError(`icon data must be set if icon is defined`), - //}, - //{ - // name: "Icon/Error/NoMediaType", - // v: &Icon{ - // Data: mustBase64Decode(svgData), - // MediaType: "", - // }, - // assertion: hasError(`icon mediatype must be set if icon is defined`), - //}, - //{ - // name: "Icon/Error/DataIsNotImage", - // v: &Icon{ - // Data: []byte("{}"), - // MediaType: "application/json", - // }, - // assertion: hasError(`icon data is not an image`), - //}, - //{ - // name: "Icon/Error/DataDoesNotMatchMediaType", - // v: &Icon{ - // Data: mustBase64Decode(svgData), - // MediaType: "image/jpeg", - // }, - // assertion: hasError(`icon media type "image/jpeg" does not match detected media type "image/svg+xml"`), - //}, - { - name: "Channel/Success/Valid", - v: ch, - assertion: require.NoError, - }, - { - name: "Channel/Error/NoName", - v: &Channel{}, - assertion: hasError(`channel name must not be empty`), - }, - { - name: "Channel/Error/NoPackage", - v: &Channel{ - Name: "light", - }, - assertion: hasError(`package must be set`), - }, - { - name: "Channel/Error/NoBundles", - v: &Channel{ - Package: pkg, - Name: "light", - }, - assertion: hasError(`channel must contain at least one bundle`), - }, - { - name: "Channel/Error/InvalidHead", - v: &Channel{ - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.0": {Name: "anakin.v0.0.0"}, - "anakin.v0.0.1": {Name: "anakin.v0.0.1"}, - }, - }, - assertion: hasError(`multiple channel heads found in graph: anakin.v0.0.0, anakin.v0.0.1`), - }, - { - name: "Channel/Error/BundleKeyNameMismatch", - v: &Channel{ - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "foo": {Name: "bar"}, - }, - }, - assertion: hasError(`bundle key "foo" does not match bundle name "bar"`), - }, - { - name: "Channel/Error/InvalidBundle", - v: &Channel{ - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.0": {Name: "anakin.v0.0.0"}, - }, - }, - assertion: hasError(`invalid bundle "anakin.v0.0.0"`), - }, - { - name: "Channel/Error/InvalidBundleChannelLink", - v: &Channel{ - Package: pkg, - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.0": { - Package: pkg, - Channel: ch, - Name: "anakin.v0.0.0", - Image: "anakin-operator:v0.0.0", - }, - }, - }, - assertion: hasError(`bundle "anakin.v0.0.0" not correctly linked to parent channel`), - }, - { - name: "Bundle/Success/Valid", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "registry.io/image", - Replaces: "anakin.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.1.0"), - property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"), - }, - }, - assertion: require.NoError, - }, - { - name: "Bundle/Success/ReplacesNotInChannel", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "registry.io/image", - Replaces: "anakin.v0.0.0", - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.1.0"), - }, - }, - assertion: require.NoError, - }, - { - name: "Bundle/Success/NoBundleImage/HaveBundleData", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "", - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.1.0"), - property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"), - property.MustBuildBundleObject([]byte("testdata")), - }, - Objects: []string{"testdata"}, - CsvJSON: "CSVjson", - }, - assertion: require.NoError, - }, - { - name: "Bundle/Error/NoBundleImage", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "", - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.1.0"), - property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"), - }, - }, - assertion: hasError(`bundle image must be set`), - }, - { - name: "Bundle/Error/NoName", - v: &Bundle{}, - assertion: hasError(`name must be set`), - }, - { - name: "Bundle/Error/NoChannel", - v: &Bundle{ - Name: "anakin.v0.1.0", - }, - assertion: hasError(`channel must be set`), - }, - { - name: "Bundle/Error/NoPackage", - v: &Bundle{ - Channel: ch, - Name: "anakin.v0.1.0", - }, - assertion: hasError(`package must be set`), - }, - { - name: "Bundle/Error/WrongPackage", - v: &Bundle{ - Package: &Package{}, - Channel: ch, - Name: "anakin.v0.1.0", - }, - assertion: hasError(`package does not match channel's package`), - }, - { - name: "Bundle/Error/InvalidProperty", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Replaces: "anakin.v0.0.1", - Properties: []property.Property{{Type: "broken", Value: json.RawMessage("")}}, - }, - assertion: hasError(`parse property[0] of type "broken": unexpected end of JSON input`), - }, - { - name: "Bundle/Error/EmptySkipsValue", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Replaces: "anakin.v0.0.1", - Properties: []property.Property{{Type: "custom", Value: json.RawMessage("{}")}}, - Skips: []string{""}, - }, - assertion: hasError(`skip[0] is empty`), - }, - { - name: "Bundle/Error/MissingPackage", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "", - Replaces: "anakin.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - Properties: []property.Property{}, - }, - assertion: hasError(`must be exactly one property with type "olm.package"`), - }, - { - name: "Bundle/Error/MultiplePackages", - v: &Bundle{ - Package: pkg, - Channel: ch, - Name: "anakin.v0.1.0", - Image: "", - Replaces: "anakin.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.1.0"), - property.MustBuildPackage("anakin", "0.2.0"), - }, - }, - assertion: hasError(`must be exactly one property with type "olm.package"`), - }, - { - name: "RelatedImage/Success/Valid", - v: RelatedImage{ - Name: "foo", - Image: "bar", - }, - assertion: require.NoError, - }, - { - name: "RelatedImage/Error/NoImage", - v: RelatedImage{ - Name: "foo", - Image: "", - }, - assertion: hasError(`image must be set`), - }, - } - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - s.assertion(t, s.v.Validate()) - }) - } -} - -func makePackageChannelBundle() (*Package, *Channel) { - bundle1 := &Bundle{ - Name: "anakin.v0.0.1", - Image: "anakin-operator:v0.0.1", - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.0.1"), - property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"), - }, - Version: semver.MustParse("0.0.1"), - } - bundle2 := &Bundle{ - Name: "anakin.v0.0.2", - Image: "anakin-operator:v0.0.2", - Replaces: "anakin.v0.0.1", - Properties: []property.Property{ - property.MustBuildPackage("anakin", "0.0.2"), - property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"), - }, - Version: semver.MustParse("0.0.2"), - } - ch := &Channel{ - Name: "light", - Bundles: map[string]*Bundle{ - "anakin.v0.0.1": bundle1, - "anakin.v0.0.2": bundle2, - }, - } - pkg := &Package{ - Name: "anakin", - DefaultChannel: ch, - Channels: map[string]*Channel{ - ch.Name: ch, - }, - } - - bundle1.Channel, bundle2.Channel = ch, ch - bundle1.Package, bundle2.Package, ch.Package = pkg, pkg, pkg - - return pkg, ch -} - -func TestAddBundle(t *testing.T) { - type spec struct { - name string - model Model - bundle Bundle - numPkgIncrease bool - numBundlesIncrease bool - pkgBundleAddedTo string - } - pkg, _ := makePackageChannelBundle() - m := Model{} - m[pkg.Name] = pkg - - bundle1 := Bundle{ - Name: "darth.vader.v0.0.1", - Replaces: "anakin.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - Package: &Package{Name: pkg.Name}, - } - ch1 := &Channel{ - Name: "darkness", - Bundles: map[string]*Bundle{ - "vader.v0.0.1": &bundle1, - }, - } - bundle1.Channel = ch1 - - bundle2 := Bundle{ - Name: "kylo.ren.v0.0.1", - Replaces: "darth.vader.v0.0.1", - Skips: []string{"anakin.v0.0.2"}, - Package: &Package{ - Name: "Empire", - Description: "The Empire Will Rise Again", - Icon: &Icon{ - MediaType: "gif", - Data: []byte("palpatineLaughing"), - }, - Channels: make(map[string]*Channel), - }, - } - ch2 := &Channel{ - Name: "darkeness", - Bundles: map[string]*Bundle{ - "kylo.ren.v0.0.1": &bundle2, - }, - } - bundle2.Channel = ch2 - bundle2.Package.Channels[ch2.Name] = ch2 - - specs := []spec{ - { - name: "AddingToExistingPackage", - bundle: bundle1, - model: m, - numPkgIncrease: false, - numBundlesIncrease: true, - pkgBundleAddedTo: bundle1.Package.Name, - }, - { - name: "AddingNewPackage", - bundle: bundle2, - model: m, - numPkgIncrease: true, - numBundlesIncrease: false, - pkgBundleAddedTo: "", - }, - } - for _, s := range specs { - t.Run(s.name, func(t *testing.T) { - existingPkgCount := len(s.model) - existingBundleCount := 0 - if s.pkgBundleAddedTo != "" { - existingBundleCount = countBundles(m, s.pkgBundleAddedTo) - } - s.model.AddBundle(s.bundle) - if s.numPkgIncrease { - assert.Equal(t, len(s.model), existingPkgCount+1) - } - if s.numBundlesIncrease { - assert.Equal(t, countBundles(m, s.pkgBundleAddedTo), existingBundleCount+1) - } - }) - } -} - -func countBundles(m Model, pkg string) int { - count := 0 - mpkg := m[pkg] - for _, ch := range mpkg.Channels { - count += len(ch.Bundles) - } - return count -} diff --git a/alpha/model/versionrelease_test.go b/alpha/model/versionrelease_test.go deleted file mode 100644 index c583b8a03..000000000 --- a/alpha/model/versionrelease_test.go +++ /dev/null @@ -1,387 +0,0 @@ -package model - -import ( - "encoding/json" - "testing" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestVersionRelease_MarshalJSON(t *testing.T) { - tests := []struct { - name string - vr *VersionRelease - expected string - }{ - { - name: "version only", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: nil, - }, - expected: `{"version":"1.2.3","release":""}`, - }, - { - name: "version with release", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: Release{ - semver.PRVersion{VersionStr: "alpha"}, - semver.PRVersion{VersionNum: 1, IsNum: true}, - }, - }, - expected: `{"version":"1.2.3","release":"alpha.1"}`, - }, - { - name: "version with empty release", - vr: &VersionRelease{ - Version: semver.MustParse("0.1.0"), - Release: nil, - }, - expected: `{"version":"0.1.0","release":""}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data, err := json.Marshal(tt.vr) - require.NoError(t, err) - assert.JSONEq(t, tt.expected, string(data)) - }) - } -} - -func TestVersionRelease_UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - expected *VersionRelease - wantErr bool - }{ - { - name: "version only", - input: `{"version":"1.2.3"}`, - expected: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: nil, - }, - }, - { - name: "version with release", - input: `{"version":"1.2.3","release":"alpha.1"}`, - expected: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: Release{ - semver.PRVersion{VersionStr: "alpha"}, - semver.PRVersion{VersionNum: 1, IsNum: true}, - }, - }, - }, - { - name: "version with empty release", - input: `{"version":"0.1.0","release":""}`, - expected: &VersionRelease{ - Version: semver.MustParse("0.1.0"), - Release: nil, - }, - }, - { - name: "invalid json", - input: `{invalid}`, - wantErr: true, - }, - { - name: "invalid version", - input: `{"version":"not-a-version"}`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var vr VersionRelease - err := json.Unmarshal([]byte(tt.input), &vr) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expected.Version, vr.Version) - assert.Equal(t, tt.expected.Release, vr.Release) - }) - } -} - -func TestVersionRelease_MarshalUnmarshalRoundTrip(t *testing.T) { - tests := []struct { - name string - vr *VersionRelease - }{ - { - name: "version only", - vr: &VersionRelease{ - Version: semver.MustParse("2.5.1"), - Release: nil, - }, - }, - { - name: "version with release", - vr: &VersionRelease{ - Version: semver.MustParse("1.0.0"), - Release: Release{ - semver.PRVersion{VersionStr: "beta"}, - semver.PRVersion{VersionNum: 2, IsNum: true}, - }, - }, - }, - { - name: "complex version with metadata", - vr: &VersionRelease{ - Version: semver.Version{ - Major: 3, - Minor: 4, - Patch: 5, - Pre: []semver.PRVersion{ - {VersionStr: "rc"}, - {VersionNum: 1, IsNum: true}, - }, - Build: []string{"build", "123"}, - }, - Release: Release{ - semver.PRVersion{VersionStr: "rel"}, - semver.PRVersion{VersionNum: 42, IsNum: true}, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Marshal - data, err := json.Marshal(tt.vr) - require.NoError(t, err) - - // Unmarshal - var result VersionRelease - err = json.Unmarshal(data, &result) - require.NoError(t, err) - - // Compare - assert.Equal(t, tt.vr.Version, result.Version) - assert.Equal(t, tt.vr.Release, result.Release) - assert.Equal(t, 0, tt.vr.Compare(&result), "round-tripped VersionRelease should compare equal") - }) - } -} - -func TestNewRelease(t *testing.T) { - tests := []struct { - name string - input string - expected Release - wantErr bool - errMsg string - }{ - { - name: "empty string", - input: "", - expected: nil, - wantErr: false, - }, - { - name: "single alphanumeric segment", - input: "alpha", - expected: Release{ - semver.PRVersion{VersionStr: "alpha"}, - }, - }, - { - name: "single numeric segment", - input: "1", - expected: Release{ - semver.PRVersion{VersionNum: 1, IsNum: true}, - }, - }, - { - name: "multiple segments", - input: "alpha.1.beta.2", - expected: Release{ - semver.PRVersion{VersionStr: "alpha"}, - semver.PRVersion{VersionNum: 1, IsNum: true}, - semver.PRVersion{VersionStr: "beta"}, - semver.PRVersion{VersionNum: 2, IsNum: true}, - }, - }, - { - name: "hyphens allowed", - input: "rc-1.beta-2", - expected: Release{ - semver.PRVersion{VersionStr: "rc-1"}, - semver.PRVersion{VersionStr: "beta-2"}, - }, - }, - { - name: "max length 20 characters", - input: "12345678901234567890", - expected: Release{ - semver.PRVersion{VersionNum: 12345678901234567890, IsNum: true}, - }, - }, - { - name: "exceeds max length", - input: "123456789012345678901", - wantErr: true, - errMsg: "exceeds maximum length of 20 characters", - }, - { - name: "leading zeros in numeric segment", - input: "01", - wantErr: true, - errMsg: "Numeric PreRelease version must not contain leading zeroes", - }, - { - name: "leading zeros in multiple digit numeric segment", - input: "001", - wantErr: true, - errMsg: "Numeric PreRelease version must not contain leading zeroes", - }, - { - name: "zero without leading zeros is valid", - input: "0", - expected: Release{ - semver.PRVersion{VersionNum: 0, IsNum: true}, - }, - }, - { - name: "alphanumeric starting with zero is valid", - input: "0alpha", - expected: Release{ - semver.PRVersion{VersionStr: "0alpha"}, - }, - }, - { - name: "invalid characters", - input: "alpha_beta", - wantErr: true, - errMsg: "Invalid character", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := NewRelease(tt.input) - if tt.wantErr { - require.Error(t, err) - if tt.errMsg != "" { - assert.Contains(t, err.Error(), tt.errMsg) - } - return - } - require.NoError(t, err) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestVersionRelease_KubernetesSafeString(t *testing.T) { - tests := []struct { - name string - vr *VersionRelease - expected string - }{ - { - name: "version only - basic", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: nil, - }, - expected: "1.2.3", - }, - { - name: "version with build metadata - plus to dash", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3+build.123"), - Release: nil, - }, - expected: "1.2.3-build.123", - }, - { - name: "version with release", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: Release{ - semver.PRVersion{VersionStr: "alpha"}, - semver.PRVersion{VersionNum: 1, IsNum: true}, - }, - }, - expected: "1.2.3-alpha.1", - }, - { - name: "version with build metadata and release", - vr: &VersionRelease{ - Version: semver.MustParse("2.0.0+beta.456"), - Release: Release{ - semver.PRVersion{VersionStr: "rc"}, - semver.PRVersion{VersionNum: 2, IsNum: true}, - }, - }, - expected: "2.0.0-beta.456-rc.2", - }, - { - name: "version with prerelease (uppercase) - lowercase applied", - vr: &VersionRelease{ - Version: semver.MustParse("1.0.0-RC.1"), - Release: nil, - }, - expected: "1.0.0-rc.1", - }, - { - name: "version with uppercase in build metadata - lowercase applied", - vr: &VersionRelease{ - Version: semver.MustParse("3.4.5+BUILD.789"), - Release: nil, - }, - expected: "3.4.5-build.789", - }, - { - name: "release with uppercase - lowercase applied", - vr: &VersionRelease{ - Version: semver.MustParse("1.2.3"), - Release: Release{ - semver.PRVersion{VersionStr: "Alpha"}, - semver.PRVersion{VersionNum: 5, IsNum: true}, - }, - }, - expected: "1.2.3-alpha.5", - }, - { - name: "complex case with prerelease, build metadata, and release", - vr: &VersionRelease{ - Version: semver.Version{ - Major: 2, - Minor: 3, - Patch: 4, - Pre: []semver.PRVersion{ - {VersionStr: "Beta"}, - {VersionNum: 1, IsNum: true}, - }, - Build: []string{"Snapshot", "20240101"}, - }, - Release: Release{ - semver.PRVersion{VersionStr: "Final"}, - semver.PRVersion{VersionNum: 10, IsNum: true}, - }, - }, - expected: "2.3.4-beta.1-snapshot.20240101-final.10", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := tt.vr.KubernetesSafeString() - assert.Equal(t, tt.expected, result) - }) - } -} diff --git a/alpha/property/property.go b/alpha/property/property.go index dcca33ce5..f445221a6 100644 --- a/alpha/property/property.go +++ b/alpha/property/property.go @@ -42,13 +42,9 @@ type Package struct { } // 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. +// DO NOT use it for any public-facing functionalities. type Channel struct { ChannelName string `json:"channelName"` - //Priority string `json:"priority"` - Priority int `json:"priority"` } type PackageRequired struct { @@ -282,11 +278,3 @@ func MustBuildCSVMetadata(csv v1alpha1.ClusterServiceVersion) Property { Provider: csv.Spec.Provider, }) } - -// 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. -func MustBuildChannelPriority(name string, priority int) Property { - return MustBuild(&Channel{ChannelName: name, Priority: priority}) -} diff --git a/alpha/template/substitutes/substitutes.go b/alpha/template/substitutes/substitutes.go index 61efad8cf..ee8d0702f 100644 --- a/alpha/template/substitutes/substitutes.go +++ b/alpha/template/substitutes/substitutes.go @@ -73,8 +73,7 @@ func (t *template) Render(ctx context.Context, reader io.Reader) (*declcfg.Decla return nil, fmt.Errorf("render: unable to create declarative config from entries: %v", err) } - _, err = declcfg.ConvertToModel(*cfg) - if err != nil { + if err = declcfg.Validate(*cfg); err != nil { return nil, fmt.Errorf("render: entries are not valid FBC: %v", err) } @@ -257,8 +256,7 @@ func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.Declara cfg.Bundles = append(cfg.Bundles, *substituteBundle) // now validate the resulting config - _, err = declcfg.ConvertToModel(*cfg) - if err != nil { + if err = declcfg.Validate(*cfg); err != nil { return fmt.Errorf("resulting config is not valid FBC: %v", err) } diff --git a/cmd/configmap-server/main.go b/cmd/configmap-server/main.go deleted file mode 100644 index 0bf97474b..000000000 --- a/cmd/configmap-server/main.go +++ /dev/null @@ -1,189 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "google.golang.org/grpc" - health "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/reflection" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/lib/dns" - "github.com/operator-framework/operator-registry/pkg/lib/log" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/server" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var rootCmd = &cobra.Command{ - Short: "configmap-server", - Long: `configmap-server reads a configmap and builds a sqlite database containing operator manifests and serves a grpc API to query it`, - - PreRunE: func(cmd *cobra.Command, args []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runCmdFunc, -} - -func init() { - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("kubeconfig", "k", "", "absolute path to kubeconfig file") - rootCmd.Flags().StringP("database", "d", "bundles.db", "name of db to output") - rootCmd.Flags().StringP("configMapName", "c", "", "name of a configmap") - rootCmd.Flags().StringP("configMapNamespace", "n", "", "namespace of a configmap") - rootCmd.Flags().StringP("port", "p", "50051", "port number to serve on") - rootCmd.Flags().StringP("termination-log", "t", "/dev/termination-log", "path to a container termination log file") - rootCmd.Flags().Bool("permissive", false, "allow registry load errors") - if err := rootCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } -} - -func main() { - if err := rootCmd.Execute(); err != nil { - logrus.Panic(err.Error()) - } -} - -func runCmdFunc(cmd *cobra.Command, args []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - // Immediately set up termination log - terminationLogPath, err := cmd.Flags().GetString("termination-log") - if err != nil { - return err - } - err = log.AddDefaultWriterHooks(terminationLogPath) - if err != nil { - logrus.WithError(err).Warn("unable to set termination log path") - } - // Ensure there is a default nsswitch config - if err := dns.EnsureNsswitch(); err != nil { - logrus.WithError(err).Warn("unable to write default nsswitch config") - } - kubeconfig, err := cmd.Flags().GetString("kubeconfig") - if err != nil { - return err - } - port, err := cmd.Flags().GetString("port") - if err != nil { - return err - } - configMapName, err := cmd.Flags().GetString("configMapName") - if err != nil { - return err - } - configMapNamespace, err := cmd.Flags().GetString("configMapNamespace") - if err != nil { - return err - } - dbName, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - logger := logrus.WithFields(logrus.Fields{"configMapName": configMapName, "configMapNamespace": configMapNamespace, "port": port}) - - client := NewClientFromConfig(kubeconfig, logger.Logger) - configMap, err := client.CoreV1().ConfigMaps(configMapNamespace).Get(ctx, configMapName, metav1.GetOptions{}) - if err != nil { - logger.Fatalf("error getting configmap: %s", err) - } - - db, err := sqlite.Open(dbName) - if err != nil { - return err - } - - sqlLoader, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return err - } - if err := sqlLoader.Migrate(ctx); err != nil { - return err - } - - configMapPopulator := sqlite.NewSQLLoaderForConfigMap(sqlLoader, *configMap) - if err := configMapPopulator.Populate(); err != nil { - err = fmt.Errorf("error loading manifests from configmap: %s", err) - if !permissive { - logger.WithError(err).Fatal("permissive mode disabled") - } - logger.WithError(err).Warn("permissive mode enabled") - } - - var store registry.Query - // nolint:staticcheck - store, err = sqlite.NewSQLLiteQuerier(dbName) - if err != nil { - logger.WithError(err).Warnf("failed to load db") - } - // nolint:staticcheck - if store == nil { - store = registry.NewEmptyQuerier() - } - - // sanity check that the db is available - tables, err := store.ListTables(ctx) - if err != nil { - logger.WithError(err).Warnf("couldn't list tables in db") - } - if len(tables) == 0 { - logger.Warn("no tables found in db") - } - - lis, err := net.Listen("tcp", ":"+port) - if err != nil { - logger.Fatalf("failed to listen: %s", err) - } - s := grpc.NewServer() - - api.RegisterRegistryServer(s, server.NewRegistryServer(store)) - health.RegisterHealthServer(s, server.NewHealthServer()) - reflection.Register(s) - - go func() { - <-ctx.Done() - logger.Info("shutting down server") - s.GracefulStop() - }() - - logger.Info("serving registry") - return s.Serve(lis) -} - -// NewClient creates a kubernetes client or bails out on on failures. -func NewClientFromConfig(kubeconfig string, logger *logrus.Logger) kubernetes.Interface { - var config *rest.Config - var err error - - if kubeconfig != "" { - logger.Infof("Loading kube client config from path %q", kubeconfig) - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - } else { - logger.Infof("Using in-cluster kube client config") - config, err = rest.InClusterConfig() - } - - if err != nil { - logger.Fatalf("Cannot load config for REST client: %s", err) - } - - return kubernetes.NewForConfigOrDie(config) -} diff --git a/cmd/initializer/main.go b/cmd/initializer/main.go deleted file mode 100644 index 742cc79a9..000000000 --- a/cmd/initializer/main.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var rootCmd = &cobra.Command{ - Short: "initializer", - Long: `initializer takes a directory of OLM manifests and outputs a sqlite database containing them - -` + sqlite.DeprecationMessage, - PersistentPreRun: func(_ *cobra.Command, _ []string) { - sqlite.LogSqliteDeprecation() - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runCmdFunc, -} - -func init() { - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("manifests", "m", "manifests", "relative path to directory of manifests") - rootCmd.Flags().StringP("output", "o", "bundles.db", "relative path to a sqlite file to create or overwrite") - rootCmd.Flags().Bool("permissive", false, "allow registry load errors") - if err := rootCmd.Flags().MarkHidden("debug"); err != nil { - panic(err) - } -} - -func main() { - if err := rootCmd.Execute(); err != nil { - panic(err) - } -} - -func runCmdFunc(cmd *cobra.Command, args []string) error { - outFilename, err := cmd.Flags().GetString("output") - if err != nil { - return err - } - manifestDir, err := cmd.Flags().GetString("manifests") - if err != nil { - return err - } - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - db, err := sqlite.Open(outFilename) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - loader := sqlite.NewSQLLoaderForDirectory(dbLoader, manifestDir) - if err := loader.Populate(); err != nil { - err = fmt.Errorf("error loading manifests from directory: %s", err) - if !permissive { - logrus.WithError(err).Fatal("permissive mode disabled") - return err - } - logrus.WithError(err).Warn("permissive mode enabled") - } - - return nil -} diff --git a/cmd/opm/alpha/render-graph/cmd.go b/cmd/opm/alpha/render-graph/cmd.go index a8a35ae5a..97d33e94a 100644 --- a/cmd/opm/alpha/render-graph/cmd.go +++ b/cmd/opm/alpha/render-graph/cmd.go @@ -60,7 +60,7 @@ $ opm alpha render-graph quay.io/operatorhubio/catalog:latest | \ } render.Refs = args - render.AllowedRefMask = action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile + render.AllowedRefMask = action.RefDCImage | action.RefDCDir render.Registry = registry cfg, err := render.Run(cmd.Context()) diff --git a/cmd/opm/index/add.go b/cmd/opm/index/add.go deleted file mode 100644 index 7e8c8cb39..000000000 --- a/cmd/opm/index/add.go +++ /dev/null @@ -1,233 +0,0 @@ -package index - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var ( - addLong = templates.LongDesc(` - Add operator bundles to an index. - - This command will add the given set of bundle images (specified by the --bundles option) to an index image (provided by the --from-index option). - - If multiple bundles are given with '--mode=replaces' (the default), bundles are added to the index by order of ascending (semver) version unless the update graph specified by replaces requires a different input order; e.g. 1.0.0 replaces 1.0.1 would result in [1.0.1, 1.0.0] instead of the [1.0.0, 1.0.1] normally expected of semver. However, for most cases (e.g. 1.0.1 replaces 1.0.0) the bundle with the highest version is used to set the default channel of the related package. - - Caveat: in replaces mode, the head of a channel is always the bundle with the highest semver. Any bundles upgrading from this channel-head will be pruned. - An upgrade graph that looks like: - 0.1.1 -> 0.1.2 -> 0.1.2-1 - will be pruned on add to: - 0.1.1 -> 0.1.2 -`) + "\n\n" + sqlite.DeprecationMessage - - addExample = templates.Examples(` - # Create an index image from scratch with a single bundle image - %[1]s --bundles quay.io/operator-framework/operator-bundle-prometheus@sha256:a3ee653ffa8a0d2bbb2fabb150a94da6e878b6e9eb07defd40dc884effde11a0 --tag quay.io/operator-framework/monitoring:1.0.0 - - # Add a single bundle image to an index image - %[1]s --bundles quay.io/operator-framework/operator-bundle-prometheus:0.15.0 --from-index quay.io/operator-framework/monitoring:1.0.0 --tag quay.io/operator-framework/monitoring:1.0.1 - - # Add multiple bundles to an index and generate a Dockerfile instead of an image - %[1]s --bundles quay.io/operator-framework/operator-bundle-prometheus:0.15.0,quay.io/operator-framework/operator-bundle-prometheus:0.22.2 --generate - `) -) - -func addIndexAddCmd(parent *cobra.Command, showAlphaHelp bool) { - indexCmd := &cobra.Command{ - Use: "add", - Short: "Add operator bundles to an index.", - Long: addLong, - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - RunE: runIndexAddCmdFunc, - Args: cobra.NoArgs, - } - - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().Bool("generate", false, "if enabled, just creates the dockerfile and saves it to local disk") - indexCmd.Flags().StringP("out-dockerfile", "d", "", "if generating the dockerfile, this flag is used to (optionally) specify a dockerfile name") - indexCmd.Flags().StringP("from-index", "f", "", "previous index to add to") - // adding empty list of strings is a valid value. - indexCmd.Flags().StringSliceP("bundles", "b", nil, "comma separated list of bundles to add") - if err := indexCmd.MarkFlagRequired("bundles"); err != nil { - logrus.Panic("Failed to set required `bundles` flag for `index add`") - } - indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command") - indexCmd.Flags().StringP("container-tool", "c", "", "tool to interact with container images (save, build, etc.). One of: [docker, podman]") - indexCmd.Flags().StringP("build-tool", "u", "", "tool to build container images. One of: [docker, podman]. Defaults to podman. Overrides part of container-tool.") - indexCmd.Flags().StringP("pull-tool", "p", "", "tool to pull container images. One of: [none, docker, podman]. Defaults to none. Overrides part of container-tool.") - indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built") - indexCmd.Flags().Bool("permissive", false, "allow registry load errors") - indexCmd.Flags().StringP("mode", "", "replaces", "graph update mode that defines how channel graphs are updated. One of: [replaces, semver, semver-skippatch]") - - indexCmd.Flags().Bool("overwrite-latest", false, "overwrite the latest bundles (channel heads) with those of the same csv name given by --bundles") - if err := indexCmd.Flags().MarkHidden("overwrite-latest"); err != nil { - logrus.Panic(err.Error()) - } - indexCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") - if !showAlphaHelp { - if err := indexCmd.Flags().MarkHidden("enable-alpha"); err != nil { - logrus.Panic(err.Error()) - } - } - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - - // Set the example after the parent has been set to get the correct command path - parent.AddCommand(indexCmd) - indexCmd.Example = fmt.Sprintf(addExample, indexCmd.CommandPath()) -} - -func runIndexAddCmdFunc(cmd *cobra.Command, _ []string) error { - generate, err := cmd.Flags().GetBool("generate") - if err != nil { - return err - } - - outDockerfile, err := cmd.Flags().GetString("out-dockerfile") - if err != nil { - return err - } - - fromIndex, err := cmd.Flags().GetString("from-index") - if err != nil { - return err - } - - bundles, err := cmd.Flags().GetStringSlice("bundles") - if err != nil { - return err - } - - binaryImage, err := cmd.Flags().GetString("binary-image") - if err != nil { - return err - } - - tag, err := cmd.Flags().GetString("tag") - if err != nil { - return err - } - - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - mode, err := cmd.Flags().GetString("mode") - if err != nil { - return err - } - - overwrite, err := cmd.Flags().GetBool("overwrite-latest") - if err != nil { - return err - } - - enableAlpha, err := cmd.Flags().GetBool("enable-alpha") - if err != nil { - return err - } - - modeEnum, err := registry.GetModeFromString(mode) - if err != nil { - return err - } - - pullTool, buildTool, err := getContainerTools(cmd) - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"bundles": bundles}) - - logger.Info("building the index") - - indexAdder := indexer.NewIndexAdder( - containertools.NewContainerTool(buildTool, containertools.PodmanTool), - containertools.NewContainerTool(pullTool, containertools.NoneTool), - logger) - - request := indexer.AddToIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Tag: tag, - Bundles: bundles, - Permissive: permissive, - Mode: modeEnum, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - Overwrite: overwrite, - EnableAlpha: enableAlpha, - } - - err = indexAdder.AddToIndex(request) - if err != nil { - return err - } - - return nil -} - -// getContainerTools returns the pull and build tools based on command line input -// to preserve backwards compatibility and alias the legacy `container-tool` parameter -func getContainerTools(cmd *cobra.Command) (string, string, error) { - buildTool, err := cmd.Flags().GetString("build-tool") - if err != nil { - return "", "", err - } - - if buildTool == "none" { - return "", "", fmt.Errorf("none is not a valid container-tool for index add") - } - - pullTool, err := cmd.Flags().GetString("pull-tool") - if err != nil { - return "", "", err - } - - containerTool, err := cmd.Flags().GetString("container-tool") - if err != nil { - return "", "", err - } - - // Backwards compatibility mode - if containerTool != "" { - if pullTool == "" && buildTool == "" { - return containerTool, containerTool, nil - } - return "", "", fmt.Errorf("container-tool cannot be set alongside pull-tool or build-tool") - } - - // Check for defaults, then return - if pullTool == "" { - pullTool = "none" - } - - if buildTool == "" { - buildTool = "podman" - } - - return pullTool, buildTool, nil -} diff --git a/cmd/opm/index/cmd.go b/cmd/opm/index/cmd.go deleted file mode 100644 index 8ee008e09..000000000 --- a/cmd/opm/index/cmd.go +++ /dev/null @@ -1,42 +0,0 @@ -package index - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -// AddCommand adds the index subcommand to the given parent command. -func AddCommand(parent *cobra.Command, showAlphaHelp bool) { - cmd := &cobra.Command{ - Use: "index", - Short: "generate operator index container images", - Long: `generate operator index container images from preexisting operator bundles - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - PersistentPreRun: func(cmd *cobra.Command, _ []string) { - sqlite.LogSqliteDeprecation() - if skipTLS, err := cmd.Flags().GetBool("skip-tls"); err == nil && skipTLS { - logrus.Warn("--skip-tls flag is set: this mode is insecure and meant for development purposes only.") - } - }, - Args: cobra.NoArgs, - } - - parent.AddCommand(cmd) - - cmd.AddCommand(newIndexDeleteCmd()) - addIndexAddCmd(cmd, showAlphaHelp) - cmd.AddCommand(newIndexExportCmd()) - cmd.AddCommand(newIndexPruneCmd()) - cmd.AddCommand(newIndexDeprecateTruncateCmd()) - cmd.AddCommand(newIndexPruneStrandedCmd()) -} diff --git a/cmd/opm/index/delete.go b/cmd/opm/index/delete.go deleted file mode 100644 index 39a905f10..000000000 --- a/cmd/opm/index/delete.go +++ /dev/null @@ -1,130 +0,0 @@ -package index - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newIndexDeleteCmd() *cobra.Command { - indexCmd := &cobra.Command{ - Use: "rm", - Short: "delete an entire operator from an index", - Long: `delete an entire operator from an index - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runIndexDeleteCmdFunc, - Args: cobra.NoArgs, - } - - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().Bool("generate", false, "if enabled, just creates the dockerfile and saves it to local disk") - indexCmd.Flags().StringP("out-dockerfile", "d", "", "if generating the dockerfile, this flag is used to (optionally) specify a dockerfile name") - indexCmd.Flags().StringP("from-index", "f", "", "previous index to delete from") - if err := indexCmd.MarkFlagRequired("from-index"); err != nil { - logrus.Panic("Failed to set required `from-index` flag for `index delete`") - } - indexCmd.Flags().StringSliceP("operators", "o", nil, "comma separated list of operators to delete") - if err := indexCmd.MarkFlagRequired("operators"); err != nil { - logrus.Panic("Failed to set required `operators` flag for `index delete`") - } - indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command") - indexCmd.Flags().StringP("container-tool", "c", "", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]") - indexCmd.Flags().StringP("build-tool", "u", "", "tool to build container images. One of: [docker, podman]. Defaults to podman. Overrides part of container-tool.") - indexCmd.Flags().StringP("pull-tool", "p", "", "tool to pull container images. One of: [none, docker, podman]. Defaults to none. Overrides part of container-tool.") - indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built") - indexCmd.Flags().Bool("permissive", false, "allow registry load errors") - - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - - return indexCmd -} - -func runIndexDeleteCmdFunc(cmd *cobra.Command, _ []string) error { - generate, err := cmd.Flags().GetBool("generate") - if err != nil { - return err - } - - outDockerfile, err := cmd.Flags().GetString("out-dockerfile") - if err != nil { - return err - } - - fromIndex, err := cmd.Flags().GetString("from-index") - if err != nil { - return err - } - - operators, err := cmd.Flags().GetStringSlice("operators") - if err != nil { - return err - } - - binaryImage, err := cmd.Flags().GetString("binary-image") - if err != nil { - return err - } - - pullTool, buildTool, err := getContainerTools(cmd) - if err != nil { - return err - } - - tag, err := cmd.Flags().GetString("tag") - if err != nil { - return err - } - - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"operators": operators}) - - logger.Info("building the index") - - indexDeleter := indexer.NewIndexDeleter( - containertools.NewContainerTool(buildTool, containertools.PodmanTool), - containertools.NewContainerTool(pullTool, containertools.NoneTool), - logger) - - request := indexer.DeleteFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Operators: operators, - Tag: tag, - Permissive: permissive, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - } - - err = indexDeleter.DeleteFromIndex(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/index/deprecatetruncate.go b/cmd/opm/index/deprecatetruncate.go deleted file mode 100644 index 33206e323..000000000 --- a/cmd/opm/index/deprecatetruncate.go +++ /dev/null @@ -1,152 +0,0 @@ -package index - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var deprecateLong = templates.LongDesc(` - Deprecate and truncate operator bundles from an index. - - Deprecated bundles will no longer be installable. Bundles that are replaced by deprecated bundles will be removed entirely from the index. - - For example: - - Given the update graph in quay.io/my/index:v1 - 1.4.0 -- replaces -> 1.3.0 -- replaces -> 1.2.0 -- replaces -> 1.1.0 - - Applying the command: - opm index deprecatetruncate --bundles "quay.io/my/bundle:1.3.0" --from-index "quay.io/my/index:v1" --tag "quay.io/my/index:v2" - - Produces the following update graph in quay.io/my/index:v2 - 1.4.0 -- replaces -> 1.3.0 [deprecated] - - Deprecating a bundle that removes the default channel is not allowed unless the head(s) of all channels are being deprecated (the package is subsequently removed from the index). - This behavior can be enabled via the allow-package-removal flag. - Changing the default channel prior to deprecation is possible by publishing a new bundle to the index. - `) + "\n\n" + sqlite.DeprecationMessage - -func newIndexDeprecateTruncateCmd() *cobra.Command { - indexCmd := &cobra.Command{ - Hidden: true, - Use: "deprecatetruncate", - Short: "Deprecate and truncate operator bundles from an index.", - Long: deprecateLong, - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - RunE: runIndexDeprecateTruncateCmdFunc, - Args: cobra.NoArgs, - } - - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().Bool("generate", false, "if enabled, just creates the dockerfile and saves it to local disk") - indexCmd.Flags().StringP("out-dockerfile", "d", "", "if generating the dockerfile, this flag is used to (optionally) specify a dockerfile name") - indexCmd.Flags().StringP("from-index", "f", "", "previous index to add to") - indexCmd.Flags().StringSliceP("bundles", "b", nil, "comma separated list of bundles to add") - if err := indexCmd.MarkFlagRequired("bundles"); err != nil { - logrus.Panic("Failed to set required `bundles` flag for `index add`") - } - indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command") - indexCmd.Flags().StringP("container-tool", "c", "", "tool to interact with container images (save, build, etc.). One of: [docker, podman]") - indexCmd.Flags().StringP("build-tool", "u", "", "tool to build container images. One of: [docker, podman]. Defaults to podman. Overrides part of container-tool.") - indexCmd.Flags().StringP("pull-tool", "p", "", "tool to pull container images. One of: [none, docker, podman]. Defaults to none. Overrides part of container-tool.") - indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built") - indexCmd.Flags().Bool("permissive", false, "allow registry load errors") - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - indexCmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") - - return indexCmd -} - -func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, _ []string) error { - generate, err := cmd.Flags().GetBool("generate") - if err != nil { - return err - } - - outDockerfile, err := cmd.Flags().GetString("out-dockerfile") - if err != nil { - return err - } - - fromIndex, err := cmd.Flags().GetString("from-index") - if err != nil { - return err - } - - bundles, err := cmd.Flags().GetStringSlice("bundles") - if err != nil { - return err - } - - binaryImage, err := cmd.Flags().GetString("binary-image") - if err != nil { - return err - } - - tag, err := cmd.Flags().GetString("tag") - if err != nil { - return err - } - - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - pullTool, buildTool, err := getContainerTools(cmd) - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"bundles": bundles}) - - logger.Info("deprecating bundles from the index") - - indexDeprecator := indexer.NewIndexDeprecator( - containertools.NewContainerTool(buildTool, containertools.PodmanTool), - containertools.NewContainerTool(pullTool, containertools.NoneTool), - logger) - - request := indexer.DeprecateFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Tag: tag, - Bundles: bundles, - Permissive: permissive, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - AllowPackageRemoval: allowPackageRemoval, - } - - err = indexDeprecator.DeprecateFromIndex(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/index/export.go b/cmd/opm/index/export.go deleted file mode 100644 index f18674b09..000000000 --- a/cmd/opm/index/export.go +++ /dev/null @@ -1,129 +0,0 @@ -package index - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var exportLong = templates.LongDesc(` - Export an operator from an index image into the appregistry format. - - This command will take an index image (specified by the --index option), parse it for the given operator(s) (set by - the --package option) and export the operator metadata into an appregistry compliant format (a package.yaml file). - - Note: the appregistry format is being deprecated in favor of the new index image and image bundle format. - `) + "\n\n" + sqlite.DeprecationMessage - -func newIndexExportCmd() *cobra.Command { - indexCmd := &cobra.Command{ - Use: "export", - Short: "Export an operator from an index into the appregistry format", - Long: exportLong, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runIndexExportCmdFunc, - Args: cobra.NoArgs, - } - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().StringP("index", "i", "", "index to get package from") - if err := indexCmd.MarkFlagRequired("index"); err != nil { - logrus.Panic("Failed to set required `index` flag for `index export`") - } - indexCmd.Flags().StringSliceP("package", "p", nil, "comma separated list of packages to export") - indexCmd.Flags().StringP("download-folder", "f", "downloaded", "directory where downloaded operator bundle(s) will be stored") - indexCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]") - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - - // Create hidden option so we can provide deprecated shorthand - indexCmd.Flags().StringSliceP("xpackage", "o", nil, "deprecated, please use --package option instead") - if err := indexCmd.Flags().MarkHidden("xpackage"); err != nil { - logrus.Panic(err.Error()) - } - - return indexCmd -} - -func runIndexExportCmdFunc(cmd *cobra.Command, _ []string) error { - index, err := cmd.Flags().GetString("index") - if err != nil { - return err - } - - pkgFlag := cmd.Flag("package") - if pkgFlag == nil { - return fmt.Errorf("unable to get the package flag") - } - - xPkgFlag := cmd.Flag("xpackage") - if xPkgFlag == nil { - return fmt.Errorf("unable to get the package flag for deprecated shorthand '-o'") - } - - if xPkgFlag.Changed && pkgFlag.Changed { - return fmt.Errorf("cannot simultaneously set '-p' and '-o' flags, remove '-o'") - } - - packages, err := cmd.Flags().GetStringSlice("package") - if err != nil { - return err - } - if xPkgFlag.Changed { - // Use the deprecated shorthand - if packages, err = cmd.Flags().GetStringSlice("xpackage"); err != nil { - return err - } - } - - downloadPath, err := cmd.Flags().GetString("download-folder") - if err != nil { - return err - } - - containerTool, err := cmd.Flags().GetString("container-tool") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"index": index, "package": packages}) - - logger.Info("export from the index") - - indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.ExportFromIndexRequest{ - Index: index, - Packages: packages, - DownloadPath: downloadPath, - ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool), - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - } - - err = indexExporter.ExportFromIndex(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/index/prune.go b/cmd/opm/index/prune.go deleted file mode 100644 index d457b2d7f..000000000 --- a/cmd/opm/index/prune.go +++ /dev/null @@ -1,131 +0,0 @@ -package index - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newIndexPruneCmd() *cobra.Command { - indexCmd := &cobra.Command{ - Use: "prune", - Short: "prune an index of all but specified packages", - Long: `prune an index of all but specified packages - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runIndexPruneCmdFunc, - Args: cobra.NoArgs, - } - - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().Bool("generate", false, "if enabled, just creates the dockerfile and saves it to local disk") - indexCmd.Flags().StringP("out-dockerfile", "d", "", "if generating the dockerfile, this flag is used to (optionally) specify a dockerfile name") - indexCmd.Flags().StringP("from-index", "f", "", "index to prune") - if err := indexCmd.MarkFlagRequired("from-index"); err != nil { - logrus.Panic("Failed to set required `from-index` flag for `index prune`") - } - indexCmd.Flags().StringSliceP("packages", "p", nil, "comma separated list of packages to keep") - if err := indexCmd.MarkFlagRequired("packages"); err != nil { - logrus.Panic("Failed to set required `packages` flag for `index prune`") - } - indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command") - indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [docker, podman]") - indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built") - indexCmd.Flags().Bool("permissive", false, "allow registry load errors") - - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - - return indexCmd -} - -func runIndexPruneCmdFunc(cmd *cobra.Command, _ []string) error { - generate, err := cmd.Flags().GetBool("generate") - if err != nil { - return err - } - - outDockerfile, err := cmd.Flags().GetString("out-dockerfile") - if err != nil { - return err - } - - fromIndex, err := cmd.Flags().GetString("from-index") - if err != nil { - return err - } - - packages, err := cmd.Flags().GetStringSlice("packages") - if err != nil { - return err - } - - binaryImage, err := cmd.Flags().GetString("binary-image") - if err != nil { - return err - } - - containerTool, err := cmd.Flags().GetString("container-tool") - if err != nil { - return err - } - - if containerTool == "none" { - return fmt.Errorf("none is not a valid container-tool for index prune") - } - - tag, err := cmd.Flags().GetString("tag") - if err != nil { - return err - } - - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"packages": packages}) - - logger.Info("pruning the index") - - indexPruner := indexer.NewIndexPruner(containertools.NewContainerTool(containerTool, containertools.PodmanTool), logger) - - request := indexer.PruneFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Packages: packages, - Tag: tag, - Permissive: permissive, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - } - - err = indexPruner.PruneFromIndex(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/index/prunestranded.go b/cmd/opm/index/prunestranded.go deleted file mode 100644 index 8b6f397dd..000000000 --- a/cmd/opm/index/prunestranded.go +++ /dev/null @@ -1,114 +0,0 @@ -package index - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newIndexPruneStrandedCmd() *cobra.Command { - indexCmd := &cobra.Command{ - Use: "prune-stranded", - Short: "prune an index of stranded bundles", - Long: `prune an index of stranded bundles - bundles that are not associated with a particular package - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runIndexPruneStrandedCmdFunc, - Args: cobra.NoArgs, - } - - indexCmd.Flags().Bool("debug", false, "enable debug logging") - indexCmd.Flags().Bool("generate", false, "if enabled, just creates the dockerfile and saves it to local disk") - indexCmd.Flags().StringP("out-dockerfile", "d", "", "if generating the dockerfile, this flag is used to (optionally) specify a dockerfile name") - indexCmd.Flags().StringP("from-index", "f", "", "index to prune") - if err := indexCmd.MarkFlagRequired("from-index"); err != nil { - logrus.Panic("Failed to set required `from-index` flag for `index prune-stranded`") - } - indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command") - indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [docker, podman]") - indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built") - - if err := indexCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } - - return indexCmd -} - -func runIndexPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { - generate, err := cmd.Flags().GetBool("generate") - if err != nil { - return err - } - - outDockerfile, err := cmd.Flags().GetString("out-dockerfile") - if err != nil { - return err - } - - fromIndex, err := cmd.Flags().GetString("from-index") - if err != nil { - return err - } - - binaryImage, err := cmd.Flags().GetString("binary-image") - if err != nil { - return err - } - - containerTool, err := cmd.Flags().GetString("container-tool") - if err != nil { - return err - } - - if containerTool == "none" { - return fmt.Errorf("none is not a valid container-tool for index prune") - } - - tag, err := cmd.Flags().GetString("tag") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{}) - - logger.Info("pruning stranded bundles from the index") - - indexPruner := indexer.NewIndexStrandedPruner(containertools.NewContainerTool(containerTool, containertools.PodmanTool), logger) - - request := indexer.PruneStrandedFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Tag: tag, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - } - - err = indexPruner.PruneStrandedFromIndex(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/migrate/cmd.go b/cmd/opm/migrate/cmd.go deleted file mode 100644 index edf496a33..000000000 --- a/cmd/opm/migrate/cmd.go +++ /dev/null @@ -1,67 +0,0 @@ -package migrate - -import ( - "log" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/alpha/action" - "github.com/operator-framework/operator-registry/alpha/action/migrations" - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func NewCmd() *cobra.Command { - var ( - migrate action.Migrate - migrateLevel string - output string - ) - cmd := &cobra.Command{ - Use: "migrate ", - Short: "Migrate a sqlite-based index image or database file to a file-based catalog", - Long: `Migrate a sqlite-based index image or database file to a file-based catalog. - -NOTE: the --output=json format produces streamable, concatenated JSON files. -These are suitable to opm and jq, but may not be supported by arbitrary JSON -parsers that assume that a file contains exactly one valid JSON object. - -` + sqlite.DeprecationMessage, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - migrate.CatalogRef = args[0] - migrate.OutputDir = args[1] - - switch output { - case "yaml": - migrate.WriteFunc = declcfg.WriteYAML - migrate.FileExt = ".yaml" - case "json": - migrate.WriteFunc = declcfg.WriteJSON - migrate.FileExt = ".json" - default: - log.Fatalf("invalid --output value %q, expected (json|yaml)", output) - } - - if migrateLevel != "" { - m, err := migrations.NewMigrations(migrateLevel) - if err != nil { - log.Fatal(err) - } - migrate.Migrations = m - } - - logrus.Infof("rendering index %q as file-based catalog", migrate.CatalogRef) - if err := migrate.Run(cmd.Context()); err != nil { - logrus.New().Fatal(err) - } - logrus.Infof("wrote rendered file-based catalog to %q\n", migrate.OutputDir) - return nil - }, - } - cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") - cmd.Flags().StringVar(&migrateLevel, "migrate-level", "", "Name of the last migration to run (default: none)\n"+migrations.HelpText()) - - return cmd -} diff --git a/cmd/opm/registry/add.go b/cmd/opm/registry/add.go deleted file mode 100644 index 12e1fa308..000000000 --- a/cmd/opm/registry/add.go +++ /dev/null @@ -1,149 +0,0 @@ -package registry - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/registry" - reg "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryAddCmd(showAlphaHelp bool) *cobra.Command { - rootCmd := &cobra.Command{ - Use: "add", - Short: "add operator bundle to operator registry DB", - Long: `add operator bundle to operator registry DB - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: addFunc, - Args: cobra.NoArgs, - } - - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to database file") - rootCmd.Flags().StringSliceP("bundle-images", "b", []string{}, "comma separated list of links to bundle image") - rootCmd.Flags().Bool("permissive", false, "allow registry load errors") - rootCmd.Flags().Bool("skip-tls", false, "use Plain HTTP for container image registries while pulling bundles") - rootCmd.Flags().Bool("skip-tls-verify", false, "skip TLS certificate verification for container image registries while pulling bundles") - rootCmd.Flags().Bool("use-http", false, "use plain HTTP for container image registries while pulling bundles") - rootCmd.Flags().String("ca-file", "", "the root certificates to use when --container-tool=none; see docker/podman docs for certificate loading instructions") - rootCmd.Flags().StringP("mode", "", "replaces", "graph update mode that defines how channel graphs are updated. One of: [replaces, semver, semver-skippatch]") - rootCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]") - rootCmd.Flags().Bool("overwrite-latest", false, "overwrite the latest bundles (channel heads) with those of the same csv name given by --bundles") - if err := rootCmd.Flags().MarkHidden("overwrite-latest"); err != nil { - logrus.Panic(err.Error()) - } - rootCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") - if !showAlphaHelp { - if err := rootCmd.Flags().MarkHidden("enable-alpha"); err != nil { - logrus.Panic(err.Error()) - } - } - if err := rootCmd.Flags().MarkDeprecated("skip-tls", "use --use-http and --skip-tls-verify instead"); err != nil { - logrus.Panic(err.Error()) - } - return rootCmd -} - -func addFunc(cmd *cobra.Command, _ []string) error { - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - caFile, err := cmd.Flags().GetString("ca-file") - if err != nil { - return err - } - fromFilename, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - bundleImages, err := cmd.Flags().GetStringSlice("bundle-images") - if err != nil { - return err - } - containerToolStr, err := cmd.Flags().GetString("container-tool") - if err != nil { - return err - } - containerTool := containertools.NewContainerTool(containerToolStr, containertools.NoneTool) - mode, err := cmd.Flags().GetString("mode") - if err != nil { - return err - } - modeEnum, err := reg.GetModeFromString(mode) - if err != nil { - return err - } - overwrite, err := cmd.Flags().GetBool("overwrite-latest") - if err != nil { - return err - } - - enableAlpha, err := cmd.Flags().GetBool("enable-alpha") - if err != nil { - return err - } - - skipTLSVerify, useHTTP, err := util.GetTLSOptions(cmd) - if err != nil { - return err - } - - if caFile != "" { - if skipTLSVerify { - return errors.New("--skip-tls-verify must be false when --ca-file is set") - } - if containerTool != containertools.NoneTool { - return fmt.Errorf("--ca-file cannot be set with --container-tool=%[1]s; "+ - "certificates must be configured specifically for %[1]s", containerTool) - } - } - - request := registry.AddToRegistryRequest{ - Permissive: permissive, - SkipTLSVerify: skipTLSVerify, - PlainHTTP: useHTTP, - CaFile: caFile, - InputDatabase: fromFilename, - Bundles: bundleImages, - Mode: modeEnum, - ContainerTool: containerTool, - Overwrite: overwrite, - EnableAlpha: enableAlpha, - } - - logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) - - if skipTLSVerify { - logger.Warn("--skip-tls-verify flag is set: this mode is insecure and meant for development purposes only.") - } - - if useHTTP { - logger.Warn("--use-http flag is set: this mode is insecure and meant for development purposes only.") - } - - logger.Info("adding to the registry") - - registryAdder := registry.NewRegistryAdder(logger) - - err = registryAdder.AddToRegistry(request) - if err != nil { - return err - } - return nil -} diff --git a/cmd/opm/registry/cmd.go b/cmd/opm/registry/cmd.go deleted file mode 100644 index dde29cf0a..000000000 --- a/cmd/opm/registry/cmd.go +++ /dev/null @@ -1,38 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -// NewOpmRegistryCmd returns the appregistry-server command -func NewOpmRegistryCmd(showAlphaHelp bool) *cobra.Command { - rootCmd := &cobra.Command{ - Use: "registry", - Short: "interact with operator-registry database", - Long: `interact with operator-registry database building, modifying and/or serving the operator-registry database - -` + sqlite.DeprecationMessage, - PersistentPreRun: func(_ *cobra.Command, _ []string) { - sqlite.LogSqliteDeprecation() - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - Args: cobra.NoArgs, - } - - rootCmd.AddCommand(newRegistryServeCmd()) - rootCmd.AddCommand(newRegistryAddCmd(showAlphaHelp)) - rootCmd.AddCommand(newRegistryRmCmd()) - rootCmd.AddCommand(newRegistryPruneCmd()) - rootCmd.AddCommand(newRegistryPruneStrandedCmd()) - rootCmd.AddCommand(newRegistryDeprecateCmd()) - - return rootCmd -} diff --git a/cmd/opm/registry/deprecatetruncate.go b/cmd/opm/registry/deprecatetruncate.go deleted file mode 100644 index 65eb66278..000000000 --- a/cmd/opm/registry/deprecatetruncate.go +++ /dev/null @@ -1,76 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryDeprecateCmd() *cobra.Command { - cmd := &cobra.Command{ - Hidden: true, - Use: "deprecatetruncate", - Short: "deprecate operator bundle from registry DB", - Long: `deprecate operator bundle from registry DB - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: deprecateFunc, - Args: cobra.NoArgs, - } - - cmd.Flags().Bool("debug", false, "enable debug logging") - cmd.Flags().StringP("database", "d", "index.db", "relative path to database file") - cmd.Flags().StringSliceP("bundle-images", "b", []string{}, "comma separated list of links to bundle image") - cmd.Flags().Bool("permissive", false, "allow registry load errors") - cmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") - - return cmd -} - -func deprecateFunc(cmd *cobra.Command, _ []string) error { - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - fromFilename, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - bundleImages, err := cmd.Flags().GetStringSlice("bundle-images") - if err != nil { - return err - } - allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") - if err != nil { - return err - } - - request := registry.DeprecateFromRegistryRequest{ - Permissive: permissive, - InputDatabase: fromFilename, - Bundles: bundleImages, - AllowPackageRemoval: allowPackageRemoval, - } - - logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) - - logger.Info("deprecating from registry") - - registryDeprecator := registry.NewRegistryDeprecator(logger) - - err = registryDeprecator.DeprecateFromRegistry(request) - if err != nil { - logger.Fatal(err) - } - return nil -} diff --git a/cmd/opm/registry/mirror.go b/cmd/opm/registry/mirror.go deleted file mode 100644 index 5beae7792..000000000 --- a/cmd/opm/registry/mirror.go +++ /dev/null @@ -1,51 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/mirror" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func MirrorCmd() *cobra.Command { - // TODO(joelanford): MirrorCmd is unused. Delete it and any other code used only by it. - o := mirror.DefaultImageIndexMirrorerOptions() - cmd := &cobra.Command{ - Hidden: true, - Use: "mirror [src image] [dest image]", - Short: "mirror an operator-registry catalog", - Long: `mirror an operator-registry catalog image from one registry to another - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, args []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: func(cmd *cobra.Command, args []string) error { - src := args[0] - dest := args[1] - - mirrorer, err := mirror.NewIndexImageMirror(o.ToOption(), mirror.WithSource(src), mirror.WithDest(dest)) - if err != nil { - return err - } - _, err = mirrorer.Mirror() - if err != nil { - return err - } - return nil - }, - Args: cobra.ExactArgs(2), - } - flags := cmd.Flags() - - cmd.Flags().Bool("debug", false, "Enable debug logging.") - flags.StringVar(&o.ManifestDir, "--to-manifests", "manifests", "Local path to store manifests.") - - return cmd -} diff --git a/cmd/opm/registry/prune.go b/cmd/opm/registry/prune.go deleted file mode 100644 index 0160e6bd2..000000000 --- a/cmd/opm/registry/prune.go +++ /dev/null @@ -1,73 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryPruneCmd() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "prune", - Short: "prune an operator registry DB of all but specified packages", - Long: `prune an operator registry DB of all but specified packages - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runRegistryPruneCmdFunc, - Args: cobra.NoArgs, - } - - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to database file") - rootCmd.Flags().StringSliceP("packages", "p", []string{}, "comma separated list of package names to be kept") - if err := rootCmd.MarkFlagRequired("packages"); err != nil { - logrus.Panic("Failed to set required `packages` flag for `registry rm`") - } - rootCmd.Flags().Bool("permissive", false, "allow registry load errors") - - return rootCmd -} - -func runRegistryPruneCmdFunc(cmd *cobra.Command, _ []string) error { - fromFilename, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - packages, err := cmd.Flags().GetStringSlice("packages") - if err != nil { - return err - } - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - request := registry.PruneFromRegistryRequest{ - Packages: packages, - InputDatabase: fromFilename, - Permissive: permissive, - } - - logger := logrus.WithFields(logrus.Fields{"packages": packages}) - - logger.Info("pruning from the registry") - - registryPruner := registry.NewRegistryPruner(logger) - - err = registryPruner.PruneFromRegistry(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/registry/prunestranded.go b/cmd/opm/registry/prunestranded.go deleted file mode 100644 index 800272a2f..000000000 --- a/cmd/opm/registry/prunestranded.go +++ /dev/null @@ -1,58 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryPruneStrandedCmd() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "prune-stranded", - Short: "prune an operator registry DB of stranded bundles", - Long: `prune an operator registry DB of stranded bundles - bundles that are not associated with a particular package - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runRegistryPruneStrandedCmdFunc, - Args: cobra.NoArgs, - } - - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to database file") - - return rootCmd -} - -func runRegistryPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { - fromFilename, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - - request := registry.PruneStrandedFromRegistryRequest{ - InputDatabase: fromFilename, - } - - logger := logrus.WithFields(logrus.Fields{}) - - logger.Info("pruning from the registry") - - registryStrandedPruner := registry.NewRegistryStrandedPruner(logger) - - err = registryStrandedPruner.PruneStrandedFromRegistry(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/registry/rm.go b/cmd/opm/registry/rm.go deleted file mode 100644 index ba884ca8d..000000000 --- a/cmd/opm/registry/rm.go +++ /dev/null @@ -1,73 +0,0 @@ -package registry - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryRmCmd() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "rm", - Short: "remove operator from operator registry DB", - Long: `Remove operator from operator registry DB - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: rmFunc, - Args: cobra.NoArgs, - } - - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to database file") - rootCmd.Flags().StringSliceP("packages", "o", nil, "comma separated list of package names to be deleted") - if err := rootCmd.MarkFlagRequired("packages"); err != nil { - logrus.Panic("Failed to set required `packages` flag for `registry rm`") - } - rootCmd.Flags().Bool("permissive", false, "allow registry load errors") - - return rootCmd -} - -func rmFunc(cmd *cobra.Command, _ []string) error { - fromFilename, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - packages, err := cmd.Flags().GetStringSlice("packages") - if err != nil { - return err - } - permissive, err := cmd.Flags().GetBool("permissive") - if err != nil { - return err - } - - request := registry.DeleteFromRegistryRequest{ - Packages: packages, - InputDatabase: fromFilename, - Permissive: permissive, - } - - logger := logrus.WithFields(logrus.Fields{"packages": packages}) - - logger.Info("removing from the registry") - - registryDeleter := registry.NewRegistryDeleter(logger) - - err = registryDeleter.DeleteFromRegistry(request) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/opm/registry/serve.go b/cmd/opm/registry/serve.go deleted file mode 100644 index a5bd65e12..000000000 --- a/cmd/opm/registry/serve.go +++ /dev/null @@ -1,185 +0,0 @@ -package registry - -import ( - "context" - "database/sql" - "fmt" - "math" - "net" - "os" - "strconv" - "time" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "google.golang.org/grpc" - health "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/reflection" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/lib/dns" - "github.com/operator-framework/operator-registry/pkg/lib/log" - "github.com/operator-framework/operator-registry/pkg/lib/tmp" - "github.com/operator-framework/operator-registry/pkg/server" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func newRegistryServeCmd() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "serve", - Short: "serve an operator-registry database", - Long: `serve an operator-registry database that is queriable using grpc - -` + sqlite.DeprecationMessage, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: serveFunc, - Args: cobra.NoArgs, - } - - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to sqlite db") - rootCmd.Flags().StringP("port", "p", "50051", "port number to serve on") - rootCmd.Flags().StringP("termination-log", "t", "/dev/termination-log", "path to a container termination log file") - rootCmd.Flags().Bool("skip-migrate", false, "do not attempt to migrate to the latest db revision when starting") - rootCmd.Flags().String("timeout-seconds", "infinite", "Timeout in seconds. This flag will be removed later.") - - return rootCmd -} - -func serveFunc(cmd *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - // Immediately set up termination log - terminationLogPath, err := cmd.Flags().GetString("termination-log") - if err != nil { - return err - } - err = log.AddDefaultWriterHooks(terminationLogPath) - if err != nil { - logrus.WithError(err).Warn("unable to set termination log path") - } - - // Ensure there is a default nsswitch config - if err := dns.EnsureNsswitch(); err != nil { - logrus.WithError(err).Warn("unable to write default nsswitch config") - } - - dbName, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - - port, err := cmd.Flags().GetString("port") - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"database": dbName, "port": port}) - - // make a writable copy of the db for migrations - tmpdb, err := tmp.CopyTmpDB(dbName) - if err != nil { - return err - } - defer os.Remove(tmpdb) - - db, err := sqlite.Open(tmpdb) - if err != nil { - return err - } - - if _, err := db.ExecContext(ctx, `PRAGMA soft_heap_limit=1`); err != nil { - logger.WithError(err).Warnf("error setting soft heap limit for sqlite") - } - - // migrate to the latest version - shouldSkipMigrate, err := cmd.Flags().GetBool("skip-migrate") - if err != nil { - return err - } - if err := migrate(ctx, shouldSkipMigrate, db); err != nil { - logger.WithError(err).Warnf("couldn't migrate db") - } - - store := sqlite.NewSQLLiteQuerierFromDb(db, sqlite.OmitManifests(true)) - - // sanity check that the db is available - tables, err := store.ListTables(ctx) - if err != nil { - logger.WithError(err).Warnf("couldn't list tables in db") - } - if len(tables) == 0 { - logger.Warn("no tables found in db") - } - - lis, err := net.Listen("tcp", ":"+port) - if err != nil { - return fmt.Errorf("failed to listen: %s", err) - } - - timeout, err := cmd.Flags().GetString("timeout-seconds") - if err != nil { - return err - } - - s := grpc.NewServer() - logger.Printf("Keeping server open for %s seconds", timeout) - if timeout != "infinite" { - timeoutInputSeconds, err := strconv.ParseUint(timeout, 10, 16) - if err != nil { - return err - } - // duration is a signed int, so capping it to prevent overflow - if timeoutInputSeconds > math.MaxInt64 { - timeoutInputSeconds = math.MaxInt64 - logger.Infof("Timeout value too large. Capping to %v.", math.MaxInt64) - } - // having capped the value to safe ranges, quiet the linter - // nolint:gosec - timeoutSeconds := int64(timeoutInputSeconds) - - timeoutDuration := time.Duration(timeoutSeconds) * time.Second - timer := time.AfterFunc(timeoutDuration, func() { - logger.Info("Timeout expired. Gracefully stopping.") - s.GracefulStop() - }) - defer timer.Stop() - } - - api.RegisterRegistryServer(s, server.NewRegistryServer(store)) - health.RegisterHealthServer(s, server.NewHealthServer()) - reflection.Register(s) - - go func() { - <-ctx.Done() - logger.Info("shutting down server") - s.GracefulStop() - }() - - logger.Info("serving registry") - return s.Serve(lis) -} - -func migrate(ctx context.Context, shouldSkipMigrate bool, db *sql.DB) error { - if shouldSkipMigrate { - return nil - } - - migrator, err := sqlite.NewSQLLiteMigrator(db) - if err != nil { - return err - } - if migrator == nil { - return fmt.Errorf("failed to load migrator") - } - - return migrator.Migrate(ctx) -} diff --git a/cmd/opm/render/cmd.go b/cmd/opm/render/cmd.go index f1923406c..e5be3cefa 100644 --- a/cmd/opm/render/cmd.go +++ b/cmd/opm/render/cmd.go @@ -13,7 +13,6 @@ import ( "github.com/operator-framework/operator-registry/alpha/action/migrations" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/cmd/opm/internal/util" - "github.com/operator-framework/operator-registry/pkg/sqlite" ) func NewCmd(showAlphaHelp bool) *cobra.Command { @@ -26,11 +25,10 @@ func NewCmd(showAlphaHelp bool) *cobra.Command { migrateLevel string ) cmd := &cobra.Command{ - Use: "render [catalog-image | catalog-directory | bundle-image | bundle-directory | sqlite-file]...", + Use: "render [catalog-image | catalog-directory | bundle-image | bundle-directory]...", Short: "Generate a stream of file-based catalog objects from catalogs and bundles", Long: `Generate a stream of file-based catalog objects to stdout from the provided -catalog images, file-based catalog directories, bundle images, and sqlite -database files. +catalog images, file-based catalog directories, and bundle images. `, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { @@ -112,6 +110,5 @@ those images actually existing. Available template variables are: - {{.Version}} : the version of the bundle ` } - cmd.Long += "\n" + sqlite.DeprecationMessage return cmd } diff --git a/cmd/opm/root/cmd.go b/cmd/opm/root/cmd.go index d23657aee..1d7973174 100644 --- a/cmd/opm/root/cmd.go +++ b/cmd/opm/root/cmd.go @@ -10,10 +10,7 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/alpha" "github.com/operator-framework/operator-registry/cmd/opm/generate" - "github.com/operator-framework/operator-registry/cmd/opm/index" initcmd "github.com/operator-framework/operator-registry/cmd/opm/init" - "github.com/operator-framework/operator-registry/cmd/opm/migrate" - "github.com/operator-framework/operator-registry/cmd/opm/registry" "github.com/operator-framework/operator-registry/cmd/opm/render" "github.com/operator-framework/operator-registry/cmd/opm/serve" "github.com/operator-framework/operator-registry/cmd/opm/validate" @@ -44,8 +41,7 @@ To view help related to alpha features, set HELP_ALPHA=true in the environment.` logrus.Panic(err.Error()) } - cmd.AddCommand(registry.NewOpmRegistryCmd(showAlphaHelp), alpha.NewCmd(showAlphaHelp), initcmd.NewCmd(), migrate.NewCmd(), serve.NewCmd(), render.NewCmd(showAlphaHelp), validate.NewCmd(), generate.NewCmd()) - index.AddCommand(cmd, showAlphaHelp) + cmd.AddCommand(alpha.NewCmd(showAlphaHelp), initcmd.NewCmd(), serve.NewCmd(), render.NewCmd(showAlphaHelp), validate.NewCmd(), generate.NewCmd()) version.AddCommand(cmd) cmd.Flags().Bool("debug", false, "enable debug logging") diff --git a/cmd/registry-server/main.go b/cmd/registry-server/main.go deleted file mode 100644 index 6ed2fc9ed..000000000 --- a/cmd/registry-server/main.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "fmt" - "net" - "os" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "google.golang.org/grpc" - health "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/reflection" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/lib/dns" - "github.com/operator-framework/operator-registry/pkg/lib/log" - "github.com/operator-framework/operator-registry/pkg/lib/tmp" - "github.com/operator-framework/operator-registry/pkg/server" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -var rootCmd = &cobra.Command{ - Short: "registry-server", - Long: `registry loads a sqlite database containing operator manifests and serves a grpc API to query it - -` + sqlite.DeprecationMessage, - PersistentPreRun: func(_ *cobra.Command, _ []string) { - sqlite.LogSqliteDeprecation() - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if debug, _ := cmd.Flags().GetBool("debug"); debug { - logrus.SetLevel(logrus.DebugLevel) - } - return nil - }, - - RunE: runCmdFunc, -} - -func init() { - rootCmd.Flags().Bool("debug", false, "enable debug logging") - rootCmd.Flags().StringP("database", "d", "bundles.db", "relative path to sqlite db") - rootCmd.Flags().StringP("port", "p", "50051", "port number to serve on") - rootCmd.Flags().StringP("termination-log", "t", "/dev/termination-log", "path to a container termination log file") - rootCmd.Flags().Bool("skip-migrate", false, "do not attempt to migrate to the latest db revision when starting") - if err := rootCmd.Flags().MarkHidden("debug"); err != nil { - logrus.Panic(err.Error()) - } -} - -func main() { - if err := rootCmd.Execute(); err != nil { - logrus.Panic(err.Error()) - } -} - -func runCmdFunc(cmd *cobra.Command, args []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - // Immediately set up termination log - terminationLogPath, err := cmd.Flags().GetString("termination-log") - if err != nil { - return err - } - err = log.AddDefaultWriterHooks(terminationLogPath) - if err != nil { - logrus.WithError(err).Warn("unable to set termination log path") - } - // Ensure there is a default nsswitch config - if err := dns.EnsureNsswitch(); err != nil { - logrus.WithError(err).Warn("unable to write default nsswitch config") - } - dbName, err := cmd.Flags().GetString("database") - if err != nil { - return err - } - - port, err := cmd.Flags().GetString("port") - if err != nil { - return err - } - - logger := logrus.WithFields(logrus.Fields{"database": dbName, "port": port}) - - // make a writable copy of the db for migrations - tmpdb, err := tmp.CopyTmpDB(dbName) - if err != nil { - return err - } - defer os.Remove(tmpdb) - - db, err := sqlite.Open(tmpdb) - if err != nil { - return err - } - - if _, err := db.ExecContext(ctx, `PRAGMA soft_heap_limit=1`); err != nil { - logger.WithError(err).Warnf("error setting soft heap limit for sqlite") - } - - // migrate to the latest version - shouldSkipMigrate, err := cmd.Flags().GetBool("skip-migrate") - if err != nil { - return err - } - if err := migrate(ctx, shouldSkipMigrate, db); err != nil { - logger.WithError(err).Warnf("couldn't migrate db") - } - - store := sqlite.NewSQLLiteQuerierFromDb(db, sqlite.OmitManifests(true)) - - // sanity check that the db is available - tables, err := store.ListTables(ctx) - if err != nil { - logger.WithError(err).Warnf("couldn't list tables in db") - } - if len(tables) == 0 { - logger.Warn("no tables found in db") - } - - lis, err := net.Listen("tcp", ":"+port) - if err != nil { - logger.Fatalf("failed to listen: %s", err) - } - s := grpc.NewServer() - - api.RegisterRegistryServer(s, server.NewRegistryServer(store)) - health.RegisterHealthServer(s, server.NewHealthServer()) - reflection.Register(s) - - go func() { - <-ctx.Done() - logger.Info("shutting down server") - s.GracefulStop() - }() - - logger.Info("serving registry") - return s.Serve(lis) -} - -func migrate(ctx context.Context, shouldSkipMigrate bool, db *sql.DB) error { - if shouldSkipMigrate { - return nil - } - - migrator, err := sqlite.NewSQLLiteMigrator(db) - if err != nil { - return err - } - if migrator == nil { - return fmt.Errorf("failed to load migrator") - } - - return migrator.Migrate(ctx) -} diff --git a/docs/contributors/add_migration.md b/docs/contributors/add_migration.md deleted file mode 100644 index 07bf13d13..000000000 --- a/docs/contributors/add_migration.md +++ /dev/null @@ -1,45 +0,0 @@ -# Add a new migration - -Migrations live in `pkg/sqlite/migrations`. - -Create a new file (and tests!) that increments the migration number: - -```sh -touch pkg/sqlite/migrations/002_migration_description.go -touch pkg/sqlite/migrations/002_migration_description_test.go -``` - -Create the migration instance and register it: - -```go -package migrations - -import ( - "context" - "database/sql" -) - -// This should increment the value from the previous migration -const MyMigrationKey = 2 - - -var myNewMigration = &Migration{ - // The id for this migration - Id: MyMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - // the up version of this migration - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - // the down version of this migration - return nil - }, -} - -// Register this migration -func init() { - migrations[MyMigrationKey] = myNewMigration -} -``` - -See the existing migrations in the `pkg/sqlite/migrations` for examples of migrations and tests. diff --git a/docs/contributors/design-proposals/db-migrations.md b/docs/contributors/design-proposals/db-migrations.md deleted file mode 100644 index 440ef74fd..000000000 --- a/docs/contributors/design-proposals/db-migrations.md +++ /dev/null @@ -1,61 +0,0 @@ -# Add database versioning and migrations to output database - -Status: Pending - -Version: alpha - -Implementation owner: TBD - -## Abstract - -Proposal to add database versioning and migration capability to the output of operator-registry commands - -## Motivation - -In an attempt at optimizing the registry database usage in OLM to allow the registry database to be stored in a container image, a method is needed to add to the database over time. Since these database files will be long living, it becomes a requirement that the database can migrate to a new schema over time. This proposal attempts to solve that problem by implementing a method with which database versions can be migrated with existing db files over time. - -## Proposal - -### Migration Method - -At a high level, the migration method is relatively straightforward. The database will contain a current migration version and when any additive operation is called with an existing database as input, the first step will be to run the migration to upgrade the database schema to the latest version. - -Rather than reinventing the database migration process, operator-registry will use an existing database migration tool that has already defined a set of semantics and conventions. This proposal suggests using the [golang-migrate](https://github.com/golang-migrate/migrate) tool for this purpose. Golang-migrate uses a set of conventions to define migrations. - -Firstly, it defines migrations as a set of sql files that live in a flat database folder. Migrations are individually defined as a pair of up and down migration scripts. These up and down scripts each define a method of going to and back from a particular database version and should be opposites semantically. Each script pair has a unique 64 bit unsigned integer identifier followed by an underscore, as well as a title, a direction, and ends in the `.sql` extension: `${version}_${title}.${direction(down/up)}.sql`. The versions are ordered, with the lowest integer value coming first and the latest version defining the current database migration version in the db. For more details on the migration format, please see https://github.com/golang-migrate/migrate/blob/master/MIGRATIONS.md. - -``` - # example migrations folder - db_migrations - ├── 201909251522_add_users_table.up.sql - ├── 201909251522_add_users_table.down.sql - ├── 201909251510_first_migration.up.sql - └── 201909251510_first_migration.down.sql -``` - -Once that migration schema is defined, whenever any additive operation is run against the database we will first use the migrate API to upgrade the schema to the latest version. See https://godoc.org/github.com/golang-migrate/migrate#Migrate.Up for more details on that API. - -### Versioning fresh databases - -One other consideration of note here is that the operator-registry database is not a long living database in the traditional sense, since the database is often created from scratch. All common migration tools do not generally account for such an edge case, so this proposal also defines a method of initializing the database at a particular migration version. Since the migration version is a matter of convention on file names, we can infer the migration version by parsing the migration folder and finding the latest migration script version. Then, once we initialize the database on startup we can use `golang-migrate` API to force the initial migration version. See https://godoc.org/github.com/golang-migrate/migrate#Migrate.Force for more details on that API. - -### Schema Definition - -One thing to note is the choice of how the database schema is defined. Currently that schema is defined in source code here in [/pkg/sqlite/load.go](https://github.com/operator-framework/operator-registry/blob/master/pkg/sqlite/load.go#L29) as a list of create table statements. - -However, once sql migrations are written the database schema is sometimes generated in two ways (through a migration upgrade or from scratch) as defined above. In that case, there are a few ways to do that. One is to leave the initial schema as is and use the migrations on a clean install as well -- but that means that the schema is not defined in one single human readable place. This can commonly be worked around by using a migration tool that can output an example of the sql schema for the purpose of development. - -The other option, and the one that this proposal defines as the solution, is to keep the schema definition in `load.go` in sync with the changes defined in each migration. In this case, it is possible for the database to go out of sync on migration vs from scratch in the case where the migration was written properly. This can be easily mitigated by writing an automated test that always ensures that some initial bundle database can be migrated to a latest version and have the same schema as a clean database. - -As a result, this implementation will require writing tests to ensure that the schema versioning is in sync between upgrade and scratch creation. - -### Choice of Migration Tool - -One point of this proposal is deciding to use the `golang-migrate` project as a method of driving migration conventions and the migrations themselves. Below is a list of criteria that this tool fulfilled when making that choice: - -- Popular and well supported -- Good documentation -- Supports lots of different database drivers, in the event that we move away from sqlite we can maintain the use of workflows around migrations -- No need to ship a migration binary, migration can be handled by importing `golang-migrate` and running in source -- Has a nice API for upgrading and reading the schema version for our scratch scenario that doesn't require us to expect a particular convention of database versioning -- Allows defining migration versions as timestamps diff --git a/go.mod b/go.mod index e7b335781..8f1a8b7e9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/distribution/distribution/v3 v3.1.1 github.com/distribution/reference v0.6.0 github.com/docker/cli v29.5.0+incompatible - github.com/golang-migrate/migrate/v4 v4.19.1 github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 @@ -18,7 +17,6 @@ require ( github.com/h2non/filetype v1.1.3 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/joelanford/ignore v0.1.1 - github.com/mattn/go-sqlite3 v1.14.44 github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2 github.com/onsi/ginkgo/v2 v2.29.0 github.com/onsi/gomega v1.41.0 @@ -35,7 +33,6 @@ require ( go.etcd.io/bbolt v1.4.3 go.podman.io/common v0.67.1 go.podman.io/image/v5 v5.39.2 - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 golang.org/x/mod v0.36.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.44.0 @@ -48,7 +45,6 @@ require ( k8s.io/apiextensions-apiserver v0.35.4 k8s.io/apimachinery v0.35.4 k8s.io/client-go v0.35.4 - k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kind v0.31.0 @@ -59,9 +55,7 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect cel.dev/expr v0.25.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/BurntSushi/toml v1.6.0 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.13.0 // indirect @@ -136,18 +130,16 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/miekg/pkcs11 v1.1.2 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -164,7 +156,6 @@ require ( github.com/redis/go-redis/extra/rediscmd/v9 v9.17.3 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.17.3 // indirect github.com/redis/go-redis/v9 v9.17.3 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/sigstore/fulcio v1.8.5 // indirect github.com/sigstore/protobuf-specs v0.5.0 // indirect @@ -204,6 +195,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.50.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/term v0.42.0 // indirect @@ -217,7 +209,6 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.35.4 // indirect - k8s.io/cli-runtime v0.35.0 // indirect k8s.io/component-base v0.35.4 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect diff --git a/go.sum b/go.sum index ebd05c450..ebdb87f84 100644 --- a/go.sum +++ b/go.sum @@ -7,13 +7,9 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -82,8 +78,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -190,8 +184,6 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= -github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= @@ -283,18 +275,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= -github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2 h1:V23nK2R2B63g2GhygF9zVGpnigmhvoZoH8d0hrZwMGY= github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2/go.mod h1:Mr897yU9FmyKaQDPtRlVKibrjz40XXyOHUfyZBPSyZU= @@ -302,8 +290,6 @@ github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3v github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/miekg/pkcs11 v1.1.2 h1:/VxmeAX5qU6Q3EwafypogwWbYryHFmF2RpkJmw3m4MQ= github.com/miekg/pkcs11 v1.1.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= @@ -316,8 +302,6 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -384,7 +368,6 @@ github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1D github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -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/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= @@ -601,7 +584,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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= @@ -719,8 +701,6 @@ k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.4 h1:vtuFqNFmF9bPRdHDL2lpK6qCTPWDreZJL4LRPwVM6ho= k8s.io/apiserver v0.35.4/go.mod h1:JnBcb+J8kFXKpZkgcbcUnPBBHi4qgBii1I7dLxFY/oo= -k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= -k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= k8s.io/component-base v0.35.4 h1:6n1tNJ87johN0Hif0Fs8K2GMthsaUwMqCebUDLYyv7U= @@ -729,8 +709,6 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY= k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc= -k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo= k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= diff --git a/pkg/api/api_to_model.go b/pkg/api/api_to_model.go deleted file mode 100644 index 50088ab4f..000000000 --- a/pkg/api/api_to_model.go +++ /dev/null @@ -1,153 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "sort" - - "github.com/blang/semver/v4" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func ConvertAPIBundleToModelBundle(b *Bundle) (*model.Bundle, error) { - bundleProps, err := convertAPIBundleToModelProperties(b) - if err != nil { - return nil, fmt.Errorf("convert properties: %v", err) - } - - relatedImages, err := getRelatedImages(b.CsvJson) - if err != nil { - return nil, fmt.Errorf("get related iamges: %v", err) - } - - vers, err := semver.Parse(b.Version) - if err != nil { - return nil, fmt.Errorf("parse version %q: %v", b.Version, err) - } - - return &model.Bundle{ - Name: b.CsvName, - Image: b.BundlePath, - Replaces: b.Replaces, - Skips: b.Skips, - SkipRange: b.SkipRange, - CsvJSON: b.CsvJson, - Objects: b.Object, - Properties: bundleProps, - RelatedImages: relatedImages, - Version: vers, - }, nil -} - -func convertAPIBundleToModelProperties(b *Bundle) ([]property.Property, error) { - // nolint:prealloc - var out []property.Property - - providedGVKs := map[property.GVK]struct{}{} - requiredGVKs := map[property.GVKRequired]struct{}{} - - foundPackageProperty := false - for i, p := range b.Properties { - switch p.Type { - case property.TypeGVK: - var v GroupVersionKind - if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} - } - k := property.GVK{Group: v.Group, Kind: v.Kind, Version: v.Version} - providedGVKs[k] = struct{}{} - case property.TypePackage: - foundPackageProperty = true - out = append(out, property.Property{ - Type: property.TypePackage, - Value: json.RawMessage(p.Value), - }) - default: - out = append(out, property.Property{ - Type: p.Type, - Value: json.RawMessage(p.Value), - }) - } - } - - for i, p := range b.Dependencies { - switch p.Type { - case property.TypeGVK: - var v GroupVersionKind - if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} - } - k := property.GVKRequired{Group: v.Group, Kind: v.Kind, Version: v.Version} - requiredGVKs[k] = struct{}{} - case property.TypePackage: - var v property.Package - if err := json.Unmarshal(json.RawMessage(p.Value), &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} - } - out = append(out, property.MustBuildPackageRequired(v.PackageName, v.Version)) - } - } - - if !foundPackageProperty { - out = append(out, property.MustBuildPackage(b.PackageName, b.Version)) - } - - for _, p := range b.ProvidedApis { - k := property.GVK{Group: p.Group, Kind: p.Kind, Version: p.Version} - if _, ok := providedGVKs[k]; !ok { - providedGVKs[k] = struct{}{} - } - } - for _, p := range b.RequiredApis { - k := property.GVKRequired{Group: p.Group, Kind: p.Kind, Version: p.Version} - if _, ok := requiredGVKs[k]; !ok { - requiredGVKs[k] = struct{}{} - } - } - - for p := range providedGVKs { - out = append(out, property.MustBuildGVK(p.Group, p.Version, p.Kind)) - } - - for p := range requiredGVKs { - out = append(out, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) - } - - for _, obj := range b.Object { - out = append(out, property.MustBuildBundleObject([]byte(obj))) - } - - sort.Slice(out, func(i, j int) bool { - if out[i].Type != out[j].Type { - return out[i].Type < out[j].Type - } - return string(out[i].Value) < string(out[j].Value) - }) - - return out, nil -} - -func getRelatedImages(csvJSON string) ([]model.RelatedImage, error) { - if len(csvJSON) == 0 { - return nil, nil - } - type csv struct { - Spec struct { - RelatedImages []struct { - Name string `json:"name"` - Image string `json:"image"` - } `json:"relatedImages"` - } `json:"spec"` - } - c := csv{} - if err := json.Unmarshal([]byte(csvJSON), &c); err != nil { - return nil, fmt.Errorf("unmarshal csv: %v", err) - } - relatedImages := []model.RelatedImage{} - for _, ri := range c.Spec.RelatedImages { - relatedImages = append(relatedImages, model.RelatedImage(ri)) - } - return relatedImages, nil -} diff --git a/pkg/api/conversion_test.go b/pkg/api/conversion_test.go deleted file mode 100644 index 3dee1c0a5..000000000 --- a/pkg/api/conversion_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package api - -import ( - "encoding/json" - "testing" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func TestConvertAPIBundleToModelBundle(t *testing.T) { - apiBundle := testAPIBundle() - expected := testModelBundle(t) - - actual, err := ConvertAPIBundleToModelBundle(&apiBundle) - require.NoError(t, err) - assertEqualsModelBundle(t, expected, *actual) -} - -func TestConvertModelBundleToAPIBundle(t *testing.T) { - modelBundle := testModelBundle(t) - modelBundle.Package = &model.Package{Name: "etcd"} - modelBundle.Channel = &model.Channel{Name: "singlenamespace-alpha"} - expected := testAPIBundle() - expected.Properties = append(expected.Properties, - &Property{Type: "olm.package.required", Value: "{\"packageName\":\"test\",\"versionRange\":\">=1.2.3 <2.0.0-0\"}"}, - &Property{Type: "olm.gvk.required", Value: "{\"group\":\"testapi.coreos.com\",\"kind\":\"Testapi\",\"version\":\"v1\"}"}, - ) - - actual, err := ConvertModelBundleToAPIBundle(modelBundle) - require.NoError(t, err) - assertEqualsAPIBundle(t, expected, *actual) -} - -const ( - csvJSON = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[\\n {\\n \\\"apiVersion\\\": \\\"etcd.database.coreos.com/v1beta2\\\",\\n \\\"kind\\\": \\\"EtcdCluster\\\",\\n \\\"metadata\\\": {\\n \\\"name\\\": \\\"example\\\"\\n },\\n \\\"spec\\\": {\\n \\\"size\\\": 3,\\n \\\"version\\\": \\\"3.2.13\\\"\\n }\\n },\\n {\\n \\\"apiVersion\\\": \\\"etcd.database.coreos.com/v1beta2\\\",\\n \\\"kind\\\": \\\"EtcdRestore\\\",\\n \\\"metadata\\\": {\\n \\\"name\\\": \\\"example-etcd-cluster-restore\\\"\\n },\\n \\\"spec\\\": {\\n \\\"etcdCluster\\\": {\\n \\\"name\\\": \\\"example-etcd-cluster\\\"\\n },\\n \\\"backupStorageType\\\": \\\"S3\\\",\\n \\\"s3\\\": {\\n \\\"path\\\": \\\"\\u003cfull-s3-path\\u003e\\\",\\n \\\"awsSecret\\\": \\\"\\u003caws-secret\\u003e\\\"\\n }\\n }\\n },\\n {\\n \\\"apiVersion\\\": \\\"etcd.database.coreos.com/v1beta2\\\",\\n \\\"kind\\\": \\\"EtcdBackup\\\",\\n \\\"metadata\\\": {\\n \\\"name\\\": \\\"example-etcd-cluster-backup\\\"\\n },\\n \\\"spec\\\": {\\n \\\"etcdEndpoints\\\": [\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\n \\\"storageType\\\":\\\"S3\\\",\\n \\\"s3\\\": {\\n \\\"path\\\": \\\"\\u003cfull-s3-path\\u003e\\\",\\n \\\"awsSecret\\\": \\\"\\u003caws-secret\\u003e\\\"\\n }\\n }\\n }\\n]\\n\",\"capabilities\":\"Full Lifecycle\",\"categories\":\"Database\",\"containerImage\":\"quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b\",\"createdAt\":\"2019-02-28 01:03:00\",\"description\":\"Create and maintain highly-available etcd clusters on Kubernetes\",\"repository\":\"https://github.com/coreos/etcd-operator\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.4\",\"namespace\":\"placeholder\"},\"spec\":{\"relatedImages\":[{\"name\":\"etcdv0.9.4\",\"image\":\"quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b\"}],\"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\"}]},\"description\":\"The etcd Operater creates and maintains highly-available etcd clusters on Kubernetes, allowing engineers to easily deploy and manage etcd clusters for their applications.\\n\\netcd 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.\\n\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` via port forwarding:\\n\\n $ kubectl --namespace default port-forward service/example-client 2379:2379\\n $ etcdctl --endpoints http://127.0.0.1:2379 get /\\n\\nOr directly to the API using the Kubernetes Service:\\n\\n $ etcdctl --endpoints http://example-client.default.svc:2379 get /\\n\\nBe sure to secure your etcd cluster (see Common Configurations) before exposing it outside of the namespace or cluster.\\n\\n\\n### Supported Features\\n\\n* **High availability** - Multiple 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* **Automated updates** - Rolling 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* **Backups included** - Create etcd backups and restore them through the etcd Operator.\\n\\n### Common Configurations\\n\\n* **Configure TLS** - Specify [static TLS certs](https://github.com/coreos/etcd-operator/blob/master/doc/user/cluster_tls.md) as Kubernetes secrets.\\n\\n* **Set Node Selector and Affinity** - [Spread your etcd Pods](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#three-member-cluster-with-node-selector-and-anti-affinity-across-nodes) across Nodes and availability zones.\\n\\n* **Set Resource Limits** - [Set the Kubernetes limit and request](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#three-member-cluster-with-resource-requirement) values for your etcd Pods.\\n\\n* **Customize Storage** - [Set a custom StorageClass](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#custom-persistentvolumeclaim-definition) that you would like to use.\\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:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b\",\"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:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b\",\"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:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b\",\"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\"},\"installModes\":[{\"supported\":true,\"type\":\"OwnNamespace\"},{\"supported\":true,\"type\":\"SingleNamespace\"},{\"supported\":false,\"type\":\"MultiNamespace\"},{\"supported\":false,\"type\":\"AllNamespaces\"}],\"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\":\"etcd-dev@googlegroups.com\",\"name\":\"etcd Community\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CNCF\"},\"replaces\":\"etcdoperator.v0.9.2\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"version\":\"0.9.4\"}}" - crdbackups = `{"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"}}` - crdclusters = `{"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"}}` - 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(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", - Replaces: "etcdoperator.v0.9.2", - Skips: nil, - Properties: []property.Property{ - property.MustBuildPackage("etcd", "0.9.4"), - 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.MustBuildBundleObject([]byte(crdbackups)), - property.MustBuildBundleObject([]byte(crdclusters)), - property.MustBuildBundleObject([]byte(csvJSON)), - property.MustBuildBundleObject([]byte(crdrestores)), - }, - CsvJSON: csvJSON, - Objects: []string{ - crdbackups, - crdclusters, - csvJSON, - crdrestores, - }, - RelatedImages: []model.RelatedImage{ - { - Name: "etcdv0.9.4", - Image: "quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b", - }, - }, - Version: semver.MustParse("0.9.4"), - } - return b -} - -func testAPIBundle() Bundle { - return Bundle{ - CsvName: "etcdoperator.v0.9.4", - PackageName: "etcd", - ChannelName: "singlenamespace-alpha", - BundlePath: "quay.io/operatorhubio/etcd:v0.9.4", - ProvidedApis: []*GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdBackup"}, - }, - RequiredApis: []*GroupVersionKind{ - {Group: "testapi.coreos.com", Version: "v1", Kind: "Testapi"}, - }, - Version: "0.9.4", - Dependencies: []*Dependency{ - {Type: "olm.package", Value: `{"packageName":"test","version":">=1.2.3 <2.0.0-0"}`}, - {Type: "olm.gvk", Value: `{"group":"testapi.coreos.com","kind":"Testapi","version":"v1"}`}, - }, - Properties: []*Property{ - {Type: "olm.package", Value: `{"packageName":"etcd","version":"0.9.4"}`}, - {Type: "olm.gvk", Value: `{"group":"etcd.database.coreos.com","kind":"EtcdBackup","version":"v1beta2"}`}, - }, - Replaces: "etcdoperator.v0.9.2", - CsvJson: csvJSON, - Object: []string{ - crdbackups, - crdclusters, - csvJSON, - crdrestores}, - } -} - -func assertEqualsModelBundle(t *testing.T, a, b model.Bundle) bool { - assert.ElementsMatch(t, a.Properties, b.Properties) - assert.ElementsMatch(t, a.Objects, b.Objects) - assert.ElementsMatch(t, a.Skips, b.Skips) - assert.ElementsMatch(t, a.RelatedImages, b.RelatedImages) - - a.Properties, b.Properties = nil, nil - a.Objects, b.Objects = nil, nil - a.Skips, b.Skips = nil, nil - a.RelatedImages, b.RelatedImages = nil, nil - - return assert.Equal(t, a, b) -} - -func assertEqualsAPIBundle(t *testing.T, a, b Bundle) bool { - assert.ElementsMatch(t, a.Properties, b.Properties) - assert.ElementsMatch(t, a.Dependencies, b.Dependencies) - assert.ElementsMatch(t, a.Object, b.Object) - assert.ElementsMatch(t, a.Skips, b.Skips) - assert.ElementsMatch(t, a.ProvidedApis, b.ProvidedApis) - assert.ElementsMatch(t, a.RequiredApis, b.RequiredApis) - - a.Properties, b.Properties = nil, nil - a.Dependencies, b.Dependencies = nil, nil - a.Object, b.Object = nil, nil - a.Skips, b.Skips = nil, nil - a.ProvidedApis, b.ProvidedApis = nil, nil - a.RequiredApis, b.RequiredApis = nil, nil - - return assert.Equal(t, a, b) -} diff --git a/pkg/api/model_to_api.go b/pkg/api/model_to_api.go deleted file mode 100644 index b3368383f..000000000 --- a/pkg/api/model_to_api.go +++ /dev/null @@ -1,212 +0,0 @@ -package api - -import ( - "encoding/base64" - "encoding/json" - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/operator-framework/api/pkg/lib/version" - "github.com/operator-framework/api/pkg/operators" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func ConvertModelBundleToAPIBundle(b model.Bundle) (*Bundle, error) { - props, err := parseProperties(b.Properties) - if err != nil { - 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{Version: 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} - } - } - - var deprecation *Deprecation - if b.Deprecation != nil { - deprecation = &Deprecation{ - Message: b.Deprecation.Message, - } - } - - apiDeps, err := convertModelPropertiesToAPIDependencies(b.Properties) - if err != nil { - return nil, fmt.Errorf("convert model properties to api dependencies: %v", err) - } - return &Bundle{ - CsvName: b.Name, - PackageName: b.Package.Name, - ChannelName: b.Channel.Name, - BundlePath: b.Image, - ProvidedApis: gvksProvidedtoAPIGVKs(props.GVKs), - RequiredApis: gvksRequirestoAPIGVKs(props.GVKsRequired), - Version: props.Packages[0].Version, - SkipRange: b.SkipRange, - Dependencies: apiDeps, - Properties: convertModelPropertiesToAPIProperties(b.Properties), - Replaces: b.Replaces, - Skips: b.Skips, - CsvJson: csvJSON, - Object: b.Objects, - Deprecation: deprecation, - }, nil -} - -func parseProperties(in []property.Property) (*property.Properties, error) { - props, err := property.Parse(in) - if err != nil { - return nil, err - } - - if len(props.Packages) != 1 { - 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 { - // nolint:prealloc - var out []*GroupVersionKind - for _, gvk := range in { - out = append(out, &GroupVersionKind{ - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - }) - } - return out -} -func gvksRequirestoAPIGVKs(in []property.GVKRequired) []*GroupVersionKind { - // nolint:prealloc - var out []*GroupVersionKind - for _, gvk := range in { - out = append(out, &GroupVersionKind{ - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - }) - } - return out -} - -func convertModelPropertiesToAPIProperties(props []property.Property) []*Property { - // nolint:prealloc - var out []*Property - for _, prop := range props { - // NOTE: This is a special case filter to prevent problems with existing client implementations that - // project bundle properties into CSV annotations and store those CSVs in a size-constrained - // storage backend (e.g. etcd via kube-apiserver). If the bundle object property has data inlined - // 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 || prop.Type == property.TypeCSVMetadata { - continue - } - - out = append(out, &Property{ - Type: prop.Type, - Value: string(prop.Value), - }) - } - return out -} - -func convertModelPropertiesToAPIDependencies(props []property.Property) ([]*Dependency, error) { - // nolint:prealloc - var out []*Dependency - for _, prop := range props { - switch prop.Type { - case property.TypeGVKRequired: - out = append(out, &Dependency{ - Type: property.TypeGVK, - Value: string(prop.Value), - }) - case property.TypePackageRequired: - var v property.PackageRequired - if err := json.Unmarshal(prop.Value, &v); err != nil { - return nil, err - } - pkg := property.MustBuildPackage(v.PackageName, v.VersionRange) - out = append(out, &Dependency{ - Type: pkg.Type, - Value: string(pkg.Value), - }) - } - } - return out, nil -} - -func convertModelRelatedImagesToCSVRelatedImages(in []model.RelatedImage) []v1alpha1.RelatedImage { - // nolint:prealloc - 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/cache/cache.go b/pkg/cache/cache.go index a02297b36..b24a7ae36 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -172,10 +172,8 @@ func (s *sliceBundleSender) Send(b *api.Bundle) error { func (c *cache) SendBundles(ctx context.Context, stream registry.BundleSender) error { transform := func(bundle *api.Bundle) { if bundle.BundlePath != "" { - // The SQLite-based server - // configures its querier to - // omit these fields when - // key path is set. + // Omit these fields when + // bundle path is set. bundle.CsvJson = "" bundle.Object = nil } @@ -367,26 +365,50 @@ func (c *cache) processPackage(ctx context.Context, reader io.Reader) (packageIn if err != nil { return nil, err } - pkgModel, err := declcfg.ConvertToModel(*pkgFbc) + pkgIndex, err := packagesFromDeclcfg(*pkgFbc) if err != nil { return nil, err } - pkgIndex, err := packagesFromModel(pkgModel) - if err != nil { - return nil, err - } - for _, p := range pkgModel { - for _, ch := range p.Channels { - for _, b := range ch.Bundles { - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) - if err != nil { - return nil, err - } - if err := c.backend.PutBundle(ctx, bundleKey{p.Name, ch.Name, b.Name}, apiBundle); err != nil { - return nil, fmt.Errorf("store bundle %q: %v", b.Name, err) + + // Build bundles from the declarative config + for _, b := range pkgFbc.Bundles { + // Find the package for this bundle + var pkg *declcfg.Package + for i := range pkgFbc.Packages { + if pkgFbc.Packages[i].Name == b.Package { + pkg = &pkgFbc.Packages[i] + break + } + } + if pkg == nil { + return nil, fmt.Errorf("bundle %q references unknown package %q", b.Name, b.Package) + } + + // Find all channels containing this bundle + var relevantChannels []declcfg.Channel + for _, ch := range pkgFbc.Channels { + if ch.Package != b.Package { + continue + } + for _, entry := range ch.Entries { + if entry.Name == b.Name { + relevantChannels = append(relevantChannels, ch) + break } } } + + apiBundle, err := declcfg.ConvertBundleToAPIBundle(b, *pkg, relevantChannels) + if err != nil { + return nil, fmt.Errorf("convert bundle %q: %v", b.Name, err) + } + + // Store the bundle for each channel it appears in + for _, ch := range relevantChannels { + if err := c.backend.PutBundle(ctx, bundleKey{b.Package, ch.Name, b.Name}, apiBundle); err != nil { + return nil, fmt.Errorf("store bundle %q: %v", b.Name, err) + } + } } return pkgIndex, nil } diff --git a/pkg/cache/json_test.go b/pkg/cache/json_test.go index b4d79c74b..bfe613f24 100644 --- a/pkg/cache/json_test.go +++ b/pkg/cache/json_test.go @@ -28,7 +28,7 @@ func TestJSON_StableDigest(t *testing.T) { // // If validFS needs to change DO NOT CHANGE the json cache implementation // in the same pull request. - require.Equal(t, "9adad9ff6cf54e4f", actualDigest) + require.Equal(t, "976864aa02f6a581", actualDigest) } func TestJSON_CheckIntegrity(t *testing.T) { diff --git a/pkg/cache/pkgs.go b/pkg/cache/pkgs.go index 31ab5ad23..749e358cf 100644 --- a/pkg/cache/pkgs.go +++ b/pkg/cache/pkgs.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/operator-framework/operator-registry/alpha/model" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/pkg/api" "github.com/operator-framework/operator-registry/pkg/registry" ) @@ -33,7 +33,7 @@ func (pkgs packageIndex) GetPackage(_ context.Context, name string) (*registry.P for _, ch := range pkg.Channels { var deprecation *registry.Deprecation if ch.Deprecation != nil { - deprecation = ®istry.Deprecation{Message: ch.Deprecation.Message} + deprecation = ®istry.Deprecation{Message: *ch.Deprecation} } channels = append(channels, registry.PackageChannel{ Name: ch.Name, @@ -48,7 +48,7 @@ func (pkgs packageIndex) GetPackage(_ context.Context, name string) (*registry.P DefaultChannelName: pkg.DefaultChannel, } if pkg.Deprecation != nil { - registryPackage.Deprecation = ®istry.Deprecation{Message: pkg.Deprecation.Message} + registryPackage.Deprecation = ®istry.Deprecation{Message: *pkg.Deprecation} } return registryPackage, nil } @@ -94,9 +94,7 @@ func (pkgs packageIndex) GetBundleThatReplaces(ctx context.Context, getBundle ge } // NOTE: iterating over a map is non-deterministic in Go, so if multiple bundles replace this one, - // the bundle returned by this function is also non-deterministic. The sqlite implementation - // is ALSO non-deterministic because it doesn't use ORDER BY, so its probably okay for this - // implementation to be non-deterministic as well. + // the bundle returned by this function is also non-deterministic. for _, b := range ch.Bundles { if bundleReplaces(b, name) { return getBundle(ctx, bundleKey{pkg.Name, ch.Name, b.Name}) @@ -116,11 +114,9 @@ func (pkgs packageIndex) GetChannelEntriesThatProvide(ctx context.Context, getBu return nil, err } if provides { - // TODO(joelanford): It seems like the SQLite query returns - // invalid entries (i.e. where bundle `Replaces` isn't actually - // in channel `ChannelName`). Is that a bug? For now, this mimics - // the sqlite server and returns seemingly invalid channel entries. - // Don't worry about this. Not used anymore. + // TODO(joelanford): This may return invalid entries (i.e. where bundle + // `Replaces` isn't actually in channel `ChannelName`). Is that a bug? + // Don't worry about this. Not used anymore. entries = append(entries, pkgs.channelEntriesForBundle(b, true)...) } @@ -133,14 +129,10 @@ func (pkgs packageIndex) GetChannelEntriesThatProvide(ctx context.Context, getBu return entries, nil } -// TODO(joelanford): Need to review the expected functionality of this function. I ran +// TODO(joelanford): Need to review the expected functionality of this function. This currently // -// some experiments with the sqlite version of this function and it seems to only return -// channel heads that provide the GVK (rather than searching down the graph if parent bundles -// don't provide the API). Based on that, this function currently looks at channel heads only. -// --- -// Separate, but possibly related, I noticed there are several channels in the channel entry -// table who's minimum depth is 1. What causes 1 to be minimum depth in some cases and 0 in others? +// only returns channel heads that provide the GVK (rather than searching down the graph if +// parent bundles don't provide the API). func (pkgs packageIndex) GetLatestChannelEntriesThatProvide(ctx context.Context, getBundle getBundleFunc, group, version, kind string) ([]*registry.ChannelEntry, error) { var entries []*registry.ChannelEntry @@ -190,19 +182,19 @@ func (pkgs packageIndex) GetBundleThatProvides(ctx context.Context, c Cache, gro } type cPkg struct { - Name string `json:"name"` - Description string `json:"description"` - Icon *model.Icon `json:"icon"` - DefaultChannel string `json:"defaultChannel"` + Name string `json:"name"` + Description string `json:"description"` + Icon *declcfg.Icon `json:"icon"` + DefaultChannel string `json:"defaultChannel"` Channels map[string]cChannel - Deprecation *model.Deprecation `json:"deprecation,omitempty"` + Deprecation *string `json:"deprecation,omitempty"` } type cChannel struct { Name string Head string Bundles map[string]cBundle - Deprecation *model.Deprecation `json:"deprecation,omitempty"` + Deprecation *string `json:"deprecation,omitempty"` } type cBundle struct { @@ -213,45 +205,120 @@ type cBundle struct { Skips []string `json:"skips"` } -func packagesFromModel(m model.Model) (map[string]cPkg, error) { +func packagesFromDeclcfg(cfg declcfg.DeclarativeConfig) (map[string]cPkg, error) { pkgs := map[string]cPkg{} - for _, p := range m { - newP := cPkg{ + + // First pass: create packages + for _, p := range cfg.Packages { + pkgs[p.Name] = cPkg{ Name: p.Name, Icon: p.Icon, Description: p.Description, - DefaultChannel: p.DefaultChannel.Name, + DefaultChannel: p.DefaultChannel, Channels: map[string]cChannel{}, - Deprecation: p.Deprecation, + Deprecation: nil, } - for _, ch := range p.Channels { - head, err := ch.Head() - if err != nil { - return nil, err - } - newCh := cChannel{ - Name: ch.Name, - Head: head.Name, - Bundles: map[string]cBundle{}, - Deprecation: ch.Deprecation, + } + + // Second pass: create channels and add bundles + for _, ch := range cfg.Channels { + pkg, ok := pkgs[ch.Package] + if !ok { + return nil, fmt.Errorf("channel %q references unknown package %q", ch.Name, ch.Package) + } + + // Find the head of this channel + head, err := findChannelHead(ch.Entries) + if err != nil { + return nil, fmt.Errorf("find head for channel %q in package %q: %v", ch.Name, ch.Package, err) + } + + newCh := cChannel{ + Name: ch.Name, + Head: head, + Bundles: map[string]cBundle{}, + Deprecation: nil, + } + + for _, entry := range ch.Entries { + newB := cBundle{ + Package: ch.Package, + Channel: ch.Name, + Name: entry.Name, + Replaces: entry.Replaces, + Skips: entry.Skips, } - for _, b := range ch.Bundles { - newB := cBundle{ - Package: b.Package.Name, - Channel: b.Channel.Name, - Name: b.Name, - Replaces: b.Replaces, - Skips: b.Skips, + newCh.Bundles[entry.Name] = newB + } + + pkg.Channels[ch.Name] = newCh + pkgs[ch.Package] = pkg + } + + // Third pass: apply deprecations + for _, d := range cfg.Deprecations { + pkg, ok := pkgs[d.Package] + if !ok { + return nil, fmt.Errorf("deprecation references unknown package %q", d.Package) + } + + for _, entry := range d.Entries { + switch entry.Reference.Schema { + case declcfg.SchemaPackage: + msg := entry.Message + pkg.Deprecation = &msg + case declcfg.SchemaChannel: + ch, ok := pkg.Channels[entry.Reference.Name] + if !ok { + return nil, fmt.Errorf("deprecation references unknown channel %q in package %q", entry.Reference.Name, d.Package) } - newCh.Bundles[b.Name] = newB + msg := entry.Message + ch.Deprecation = &msg + pkg.Channels[entry.Reference.Name] = ch } - newP.Channels[ch.Name] = newCh } - pkgs[p.Name] = newP + pkgs[d.Package] = pkg } + return pkgs, nil } +// findChannelHead finds the head bundle of a channel by analyzing the replaces chain. +// The head is the bundle that is not replaced by any other bundle in the channel. +func findChannelHead(entries []declcfg.ChannelEntry) (string, error) { + if len(entries) == 0 { + return "", fmt.Errorf("channel has no entries") + } + + // Build a map of bundles that are replaced + replaced := make(map[string]bool) + for _, entry := range entries { + if entry.Replaces != "" { + replaced[entry.Replaces] = true + } + for _, skip := range entry.Skips { + replaced[skip] = true + } + } + + // Find bundles that are not replaced by anything + var heads []string + for _, entry := range entries { + if !replaced[entry.Name] { + heads = append(heads, entry.Name) + } + } + + if len(heads) == 0 { + return "", fmt.Errorf("channel has circular replaces chain, no head found") + } + if len(heads) > 1 { + return "", fmt.Errorf("channel has multiple heads: %v", heads) + } + + return heads[0], nil +} + func bundleReplaces(b cBundle, name string) bool { if b.Replaces == name { return true diff --git a/pkg/cache/pogrebv1_test.go b/pkg/cache/pogrebv1_test.go index 4fee9f292..3367e60d7 100644 --- a/pkg/cache/pogrebv1_test.go +++ b/pkg/cache/pogrebv1_test.go @@ -28,7 +28,7 @@ func TestPogrebV1_StableDigest(t *testing.T) { // // If validFS needs to change DO NOT CHANGE the json cache implementation // in the same pull request. - require.Equal(t, "485a767449dd66d4", actualDigest) + require.Equal(t, "3cc6655887a2d5ce", actualDigest) } func TestPogrebV1_CheckIntegrity(t *testing.T) { diff --git a/pkg/lib/config/validate.go b/pkg/lib/config/validate.go index 34f2bf041..f559ca24b 100644 --- a/pkg/lib/config/validate.go +++ b/pkg/lib/config/validate.go @@ -20,13 +20,6 @@ func Validate(ctx context.Context, root fs.FS) error { if err != nil { return err } - // Validate the config using model validation: - // This will convert declcfg objects to intermediate model objects that are - // also used for serve and add commands. The conversion process will run - // validation for the model objects and ensure they are valid. - _, err = declcfg.ConvertToModel(*cfg) - if err != nil { - return err - } - return nil + // Validate the config + return declcfg.Validate(*cfg) } diff --git a/pkg/lib/indexer/indexer.go b/pkg/lib/indexer/indexer.go deleted file mode 100644 index dd24289f2..000000000 --- a/pkg/lib/indexer/indexer.go +++ /dev/null @@ -1,722 +0,0 @@ -package indexer - -import ( - "context" - "errors" - "fmt" - "io" - "math/rand" - "os" - "path" - "path/filepath" - "strconv" - "sync" - - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" - "github.com/operator-framework/operator-registry/pkg/image/execregistry" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-registry/pkg/lib/certs" - "github.com/operator-framework/operator-registry/pkg/lib/registry" - pregistry "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -const ( - defaultDockerfileName = "index.Dockerfile" - defaultImageTag = "operator-registry-index:latest" - defaultDatabaseFolder = "database" - defaultDatabaseFile = "index.db" - tmpDirPrefix = "index_tmp_" - tmpBuildDirPrefix = "index_build_tmp" - concurrencyLimitForExport = 10 -) - -//nolint:staticcheck // ST1005: error message intentionally ends with punctuation (URL) -var ErrFileBasedCatalogPrune = errors.New("`opm index prune` only supports sqlite-based catalogs. See https://github.com/redhat-openshift-ecosystem/community-operators-prod/issues/793 for instructions on pruning a plaintext files backed catalog.") - -// ImageIndexer is a struct implementation of the Indexer interface -type ImageIndexer struct { - DockerfileGenerator containertools.DockerfileGenerator - CommandRunner containertools.CommandRunner - LabelReader containertools.LabelReader - RegistryAdder registry.RegistryAdder - RegistryDeleter registry.RegistryDeleter - RegistryPruner registry.RegistryPruner - RegistryStrandedPruner registry.RegistryStrandedPruner - RegistryDeprecator registry.RegistryDeprecator - BuildTool containertools.ContainerTool - PullTool containertools.ContainerTool - Logger *logrus.Entry -} - -// AddToIndexRequest defines the parameters to send to the AddToIndex API -type AddToIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Bundles []string - Tag string - Mode pregistry.Mode - CaFile string - SkipTLSVerify bool - PlainHTTP bool - Overwrite bool - EnableAlpha bool -} - -// AddToIndex is an aggregate API used to generate a registry index image with additional bundles -func (i ImageIndexer) AddToIndex(request AddToIndexRequest) error { - buildDir, outDockerfile, cleanup, err := buildContext(request.Generate, request.OutDockerfile) - defer cleanup() - if err != nil { - return err - } - - databasePath, err := i.ExtractDatabase(buildDir, request.FromIndex, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - // Run opm registry add on the database - addToRegistryReq := registry.AddToRegistryRequest{ - Bundles: request.Bundles, - InputDatabase: databasePath, - Permissive: request.Permissive, - Mode: request.Mode, - SkipTLSVerify: request.SkipTLSVerify, - PlainHTTP: request.PlainHTTP, - ContainerTool: i.PullTool, - Overwrite: request.Overwrite, - EnableAlpha: request.EnableAlpha, - } - - // Add the bundles to the registry - err = i.RegistryAdder.AddToRegistry(addToRegistryReq) - if err != nil { - i.Logger.WithError(err).Debugf("unable to add bundle to registry") - return err - } - - // generate the dockerfile - dockerfile := i.DockerfileGenerator.GenerateIndexDockerfile(request.BinarySourceImage, databasePath) - err = write(dockerfile, outDockerfile, i.Logger) - if err != nil { - return err - } - - if request.Generate { - return nil - } - - // build the dockerfile - err = build(outDockerfile, request.Tag, i.CommandRunner, i.Logger) - if err != nil { - return err - } - - return nil -} - -// DeleteFromIndexRequest defines the parameters to send to the DeleteFromIndex API -type DeleteFromIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Tag string - Operators []string - SkipTLSVerify bool - PlainHTTP bool - CaFile string -} - -// DeleteFromIndex is an aggregate API used to generate a registry index image -// without specific operators -func (i ImageIndexer) DeleteFromIndex(request DeleteFromIndexRequest) error { - buildDir, outDockerfile, cleanup, err := buildContext(request.Generate, request.OutDockerfile) - defer cleanup() - if err != nil { - return err - } - - databasePath, err := i.ExtractDatabase(buildDir, request.FromIndex, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - // Run opm registry delete on the database - deleteFromRegistryReq := registry.DeleteFromRegistryRequest{ - Packages: request.Operators, - InputDatabase: databasePath, - Permissive: request.Permissive, - } - - // Delete the bundles from the registry - err = i.RegistryDeleter.DeleteFromRegistry(deleteFromRegistryReq) - if err != nil { - return err - } - - // generate the dockerfile - dockerfile := i.DockerfileGenerator.GenerateIndexDockerfile(request.BinarySourceImage, databasePath) - err = write(dockerfile, outDockerfile, i.Logger) - if err != nil { - return err - } - - if request.Generate { - return nil - } - - // build the dockerfile - err = build(outDockerfile, request.Tag, i.CommandRunner, i.Logger) - if err != nil { - return err - } - - return nil -} - -// PruneStrandedFromIndexRequest defines the parameters to send to the PruneStrandedFromIndex API -type PruneStrandedFromIndexRequest struct { - Generate bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Tag string - CaFile string - SkipTLSVerify bool - PlainHTTP bool -} - -// PruneStrandedFromIndex is an aggregate API used to generate a registry index image -// that has removed stranded bundles from the index -func (i ImageIndexer) PruneStrandedFromIndex(request PruneStrandedFromIndexRequest) error { - buildDir, outDockerfile, cleanup, err := buildContext(request.Generate, request.OutDockerfile) - defer cleanup() - if err != nil { - return err - } - - databasePath, err := i.ExtractDatabase(buildDir, request.FromIndex, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - // Run opm registry prune-stranded on the database - pruneStrandedFromRegistryReq := registry.PruneStrandedFromRegistryRequest{ - InputDatabase: databasePath, - } - - // Delete the stranded bundles from the registry - err = i.RegistryStrandedPruner.PruneStrandedFromRegistry(pruneStrandedFromRegistryReq) - if err != nil { - return err - } - - // generate the dockerfile - dockerfile := i.DockerfileGenerator.GenerateIndexDockerfile(request.BinarySourceImage, databasePath) - err = write(dockerfile, outDockerfile, i.Logger) - if err != nil { - return err - } - - if request.Generate { - return nil - } - - // build the dockerfile - err = build(outDockerfile, request.Tag, i.CommandRunner, i.Logger) - if err != nil { - return err - } - return nil -} - -// PruneFromIndexRequest defines the parameters to send to the PruneFromIndex API -type PruneFromIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Tag string - Packages []string - CaFile string - SkipTLSVerify bool - PlainHTTP bool -} - -func (i ImageIndexer) PruneFromIndex(request PruneFromIndexRequest) error { - buildDir, outDockerfile, cleanup, err := buildContext(request.Generate, request.OutDockerfile) - defer cleanup() - if err != nil { - return err - } - - databasePath, err := i.ExtractDatabase(buildDir, request.FromIndex, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - // Run opm registry prune on the database - pruneFromRegistryReq := registry.PruneFromRegistryRequest{ - Packages: request.Packages, - InputDatabase: databasePath, - Permissive: request.Permissive, - } - - // Prune the bundles from the registry - err = i.RegistryPruner.PruneFromRegistry(pruneFromRegistryReq) - if err != nil { - return err - } - - // generate the dockerfile - dockerfile := i.DockerfileGenerator.GenerateIndexDockerfile(request.BinarySourceImage, databasePath) - err = write(dockerfile, outDockerfile, i.Logger) - if err != nil { - return err - } - - if request.Generate { - return nil - } - - // build the dockerfile - err = build(outDockerfile, request.Tag, i.CommandRunner, i.Logger) - if err != nil { - return err - } - - return nil -} - -// ExtractDatabase sets a temp directory for unpacking an image -func (i ImageIndexer) ExtractDatabase(buildDir, fromIndex, caFile string, skipTLSVerify, plainHTTP bool) (string, error) { - tmpDir, err := os.MkdirTemp("./", tmpDirPrefix) - if err != nil { - return "", err - } - defer os.RemoveAll(tmpDir) - - databaseFile, err := i.getDatabaseFile(tmpDir, fromIndex, caFile, skipTLSVerify, plainHTTP) - if err != nil { - return "", err - } - // copy the index to the database folder in the build directory - return copyDatabaseTo(databaseFile, filepath.Join(buildDir, defaultDatabaseFolder)) -} - -func (i ImageIndexer) getDatabaseFile(workingDir, fromIndex, caFile string, skipTLSVerify, plainHTTP bool) (string, error) { - if fromIndex == "" { - return path.Join(workingDir, defaultDatabaseFile), nil - } - - // Pull the fromIndex - i.Logger.Infof("Pulling previous image %s to get metadata", fromIndex) - - var reg image.Registry - var rerr error - switch i.PullTool { - case containertools.NoneTool: - rootCAs, err := certs.RootCAs(caFile) - if err != nil { - return "", fmt.Errorf("failed to get RootCAs: %v", err) - } - reg, rerr = containerdregistry.NewRegistry( - containerdregistry.SkipTLSVerify(skipTLSVerify), - containerdregistry.WithPlainHTTP(plainHTTP), - containerdregistry.WithLog(i.Logger), - containerdregistry.WithRootCAs(rootCAs)) - case containertools.PodmanTool: - fallthrough - case containertools.DockerTool: - reg, rerr = execregistry.NewRegistry(i.PullTool, i.Logger, containertools.SkipTLS(plainHTTP)) - } - if rerr != nil { - return "", rerr - } - defer func() { - if err := reg.Destroy(); err != nil { - i.Logger.WithError(err).Warn("error destroying local cache") - } - }() - - imageRef := image.SimpleReference(fromIndex) - - if err := reg.Pull(context.TODO(), imageRef); err != nil { - return "", err - } - - // Get the old index image's dbLocationLabel to find this path - labels, err := reg.Labels(context.TODO(), imageRef) - if err != nil { - return "", err - } - - dbLocation, ok := labels[containertools.DbLocationLabel] - if !ok { - if _, ok := labels[containertools.ConfigsLocationLabel]; ok { - return "", ErrFileBasedCatalogPrune - } - return "", fmt.Errorf("index image %s missing label %s", fromIndex, containertools.DbLocationLabel) - } - - if err := reg.Unpack(context.TODO(), imageRef, workingDir); err != nil { - return "", err - } - - return path.Join(workingDir, dbLocation), nil -} - -func copyDatabaseTo(databaseFile, targetDir string) (string, error) { - // create the containing folder if it doesn't exist - if _, err := os.Stat(targetDir); os.IsNotExist(err) { - if err := os.MkdirAll(targetDir, 0777); err != nil { - return "", err - } - } else if err != nil { - return "", err - } - - // Open the database file in the working dir - from, err := os.OpenFile(databaseFile, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - return "", err - } - defer from.Close() - - dbFile := path.Join(targetDir, defaultDatabaseFile) - - // define the path to copy to the database/index.db file - to, err := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return "", err - } - defer to.Close() - - // copy to the destination directory - _, err = io.Copy(to, from) - return to.Name(), err -} - -func buildContext(generate bool, requestedDockerfile string) (string, string, func(), error) { - var buildDir, outDockerfile string - // set cleanup to a no-op until explicitly set - cleanup := func() {} - - if generate { - buildDir = "./" - if len(requestedDockerfile) == 0 { - outDockerfile = defaultDockerfileName - } else { - outDockerfile = requestedDockerfile - } - cleanup = func() {} - return buildDir, outDockerfile, cleanup, nil - } - - // set a temp directory for building the new image - buildDir, err := os.MkdirTemp(".", tmpBuildDirPrefix) - if err != nil { - return "", "", cleanup, err - } - cleanup = func() { - os.RemoveAll(buildDir) - } - - if len(requestedDockerfile) > 0 { - outDockerfile = requestedDockerfile - return buildDir, outDockerfile, cleanup, nil - } - - // generate a temp dockerfile if needed - tempDockerfile, err := os.CreateTemp(".", defaultDockerfileName) - if err != nil { - defer cleanup() - return "", "", cleanup, err - } - outDockerfile = tempDockerfile.Name() - cleanup = func() { - os.RemoveAll(buildDir) - os.Remove(outDockerfile) - } - - return buildDir, outDockerfile, cleanup, nil -} - -func build(dockerfilePath, imageTag string, commandRunner containertools.CommandRunner, logger *logrus.Entry) error { - if imageTag == "" { - imageTag = defaultImageTag - } - - logger.Debugf("building container image: %s", imageTag) - - err := commandRunner.Build(dockerfilePath, imageTag) - if err != nil { - return err - } - - return nil -} - -func write(dockerfileText, outDockerfile string, logger *logrus.Entry) error { - if outDockerfile == "" { - outDockerfile = defaultDockerfileName - } - - logger.Infof("writing dockerfile: %s", outDockerfile) - - f, err := os.Create(outDockerfile) - if err != nil { - return err - } - - _, err = f.WriteString(dockerfileText) - if err != nil { - return err - } - - return nil -} - -// ExportFromIndexRequest defines the parameters to send to the ExportFromIndex API -type ExportFromIndexRequest struct { - Index string - Packages []string - DownloadPath string - ContainerTool containertools.ContainerTool - CaFile string - SkipTLSVerify bool - PlainHTTP bool -} - -// ExportFromIndex is an aggregate API used to specify operators from -// an index image -func (i ImageIndexer) ExportFromIndex(request ExportFromIndexRequest) error { - // set a temp directory - workingDir, err := os.MkdirTemp("./", tmpDirPrefix) - if err != nil { - return err - } - defer os.RemoveAll(workingDir) - - // extract the index database to the file - databaseFile, err := i.getDatabaseFile(workingDir, request.Index, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - db, err := sqlite.Open(databaseFile) - if err != nil { - return err - } - defer db.Close() - - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - - // fetch all packages from the index image if packages is empty - if len(request.Packages) == 0 { - request.Packages, err = dbQuerier.ListPackages(context.TODO()) - if err != nil { - return err - } - } - - bundles, err := getBundlesToExport(dbQuerier, request.Packages) - if err != nil { - return err - } - - i.Logger.Infof("Preparing to pull bundles %+q", bundles) - - // Creating downloadPath dir - if err := os.MkdirAll(request.DownloadPath, 0777); err != nil { - return err - } - - var errs []error - var wg sync.WaitGroup - wg.Add(len(bundles)) - var mu = &sync.Mutex{} - - sem := make(chan struct{}, concurrencyLimitForExport) - - for bundleImage, bundleDir := range bundles { - go func(bundleImage string, bundleDir bundleDirPrefix) { - defer wg.Done() - - sem <- struct{}{} - defer func() { - <-sem - }() - - // generate a random folder name if bundle version is empty - if bundleDir.bundleVersion == "" { - // nolint:gosec - bundleDir.bundleVersion = strconv.Itoa(rand.Intn(10000)) - } - exporter := bundle.NewExporterForBundle(bundleImage, filepath.Join(request.DownloadPath, bundleDir.pkgName, bundleDir.bundleVersion), request.ContainerTool) - if err := exporter.Export(request.SkipTLSVerify, request.PlainHTTP); err != nil { - err = fmt.Errorf("exporting bundle image:%s failed with %s", bundleImage, err) - mu.Lock() - errs = append(errs, err) - mu.Unlock() - } - }(bundleImage, bundleDir) - } - // Wait for all the go routines to finish export - wg.Wait() - - if errs != nil { - return utilerrors.NewAggregate(errs) - } - - for _, packageName := range request.Packages { - err := generatePackageYaml(dbQuerier, packageName, filepath.Join(request.DownloadPath, packageName)) - if err != nil { - errs = append(errs, err) - } - } - return utilerrors.NewAggregate(errs) -} - -type bundleDirPrefix struct { - pkgName, bundleVersion string -} - -func getBundlesToExport(dbQuerier pregistry.Query, packages []string) (map[string]bundleDirPrefix, error) { - bundleMap := make(map[string]bundleDirPrefix) - - for _, packageName := range packages { - bundlesForPackage, err := dbQuerier.GetBundlesForPackage(context.TODO(), packageName) - if err != nil { - return nil, err - } - for k := range bundlesForPackage { - bundleMap[k.BundlePath] = bundleDirPrefix{pkgName: packageName, bundleVersion: k.Version} - } - } - - return bundleMap, nil -} - -func generatePackageYaml(dbQuerier pregistry.Query, packageName, downloadPath string) error { - var errs []error - - defaultChannel, err := dbQuerier.GetDefaultChannelForPackage(context.TODO(), packageName) - if err != nil { - return err - } - - channelList, err := dbQuerier.ListChannels(context.TODO(), packageName) - if err != nil { - return err - } - - channels := []pregistry.PackageChannel{} - for _, ch := range channelList { - csvName, err := dbQuerier.GetCurrentCSVNameForChannel(context.TODO(), packageName, ch) - if err != nil { - err = fmt.Errorf("error exporting bundle from image: %s", err) - errs = append(errs, err) - continue - } - channels = append(channels, - pregistry.PackageChannel{ - Name: ch, - CurrentCSVName: csvName, - }) - } - - manifest := pregistry.PackageManifest{ - PackageName: packageName, - DefaultChannelName: defaultChannel, - Channels: channels, - } - - manifestBytes, err := yaml.Marshal(&manifest) - if err != nil { - errs = append(errs, err) - return utilerrors.NewAggregate(errs) - } - - err = bundle.WriteFile("package.yaml", downloadPath, manifestBytes) - if err != nil { - errs = append(errs, err) - } - - return utilerrors.NewAggregate(errs) -} - -// DeprecateFromIndexRequest defines the parameters to send to the PruneFromIndex API -type DeprecateFromIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Bundles []string - Tag string - CaFile string - SkipTLSVerify bool - PlainHTTP bool - AllowPackageRemoval bool -} - -// DeprecateFromIndex takes a DeprecateFromIndexRequest and deprecates the requested -// bundles. -func (i ImageIndexer) DeprecateFromIndex(request DeprecateFromIndexRequest) error { - buildDir, outDockerfile, cleanup, err := buildContext(request.Generate, request.OutDockerfile) - defer cleanup() - if err != nil { - return err - } - - databasePath, err := i.ExtractDatabase(buildDir, request.FromIndex, request.CaFile, request.SkipTLSVerify, request.PlainHTTP) - if err != nil { - return err - } - - deprecateFromRegistryReq := registry.DeprecateFromRegistryRequest{ - Bundles: request.Bundles, - InputDatabase: databasePath, - Permissive: request.Permissive, - AllowPackageRemoval: request.AllowPackageRemoval, - } - - // Deprecate the bundles from the registry - err = i.RegistryDeprecator.DeprecateFromRegistry(deprecateFromRegistryReq) - if err != nil { - return err - } - - // generate the dockerfile - dockerfile := i.DockerfileGenerator.GenerateIndexDockerfile(request.BinarySourceImage, databasePath) - err = write(dockerfile, outDockerfile, i.Logger) - if err != nil { - return err - } - - if request.Generate { - return nil - } - - // build the dockerfile with requested tooling - err = build(outDockerfile, request.Tag, i.CommandRunner, i.Logger) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/lib/indexer/indexer_test.go b/pkg/lib/indexer/indexer_test.go deleted file mode 100644 index 570b1eabd..000000000 --- a/pkg/lib/indexer/indexer_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package indexer - -import ( - "os" - "reflect" - "sort" - "testing" - - "sigs.k8s.io/yaml" - - pregistry "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func TestGetBundlesToExport(t *testing.T) { - expected := []string{"quay.io/olmtest/example-bundle:etcdoperator.v0.9.2", "quay.io/olmtest/example-bundle:etcdoperator.v0.9.0", - "quay.io/olmtest/example-bundle:etcdoperator.v0.6.1"} - sort.Strings(expected) - - db, err := sqlite.Open("./testdata/bundles.db") - if err != nil { - t.Fatalf("opening db: %s", err) - } - defer db.Close() - - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - if err != nil { - t.Fatalf("creating querier: %s", err) - } - - bundleMap, err := getBundlesToExport(dbQuerier, []string{"etcd"}) - if err != nil { - t.Fatalf("exporting bundles from db: %s", err) - } - - bundleImages := make([]string, 0, len(bundleMap)) - for bundlePath := range bundleMap { - bundleImages = append(bundleImages, bundlePath) - } - - sort.Strings(bundleImages) - - if !reflect.DeepEqual(expected, bundleImages) { - t.Fatalf("exporting images: expected matching bundlepaths: expected %#v got %#v", expected, bundleImages) - } -} - -func TestGeneratePackageYaml(t *testing.T) { - db, err := sqlite.Open("./testdata/bundles.db") - if err != nil { - t.Fatalf("opening db: %s", err) - } - defer db.Close() - - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - if err != nil { - t.Fatalf("creating querier: %s", err) - } - - err = generatePackageYaml(dbQuerier, "etcd", ".") - if err != nil { - t.Fatalf("writing package.yaml: %s", err) - } - - var expected pregistry.PackageManifest - expectedBytes, _ := os.ReadFile("./testdata/package.yaml") - err = yaml.Unmarshal(expectedBytes, &expected) - if err != nil { - t.Fatalf("unmarshaling: %s", err) - } - - var actual pregistry.PackageManifest - actualBytes, _ := os.ReadFile("./package.yaml") - err = yaml.Unmarshal(actualBytes, &actual) - if err != nil { - t.Fatalf("unmarshaling: %s", err) - } - - if !reflect.DeepEqual(expected, actual) { - t.Fatalf("comparing package.yaml: expected #%v actual #%v", expected, actual) - } - - _ = os.RemoveAll("./package.yaml") -} - -func TestBuildContext(t *testing.T) { - // TODO(): Test does not currently have a clean way - // of testing the generated returned values such as - // outDockerfile and buildDir. - - defaultBuildDirOnGenerate := "./" - fooDockerfile := "foo.Dockerfile" - defaultDockerfile := defaultDockerfileName - - cases := []struct { - generate bool - requestedDockerfile string - expectedBuildDir *string // return values not checked if nil - expectedOutDockerfile *string // return values not checked if nil - }{ - { - generate: true, - requestedDockerfile: "", - expectedOutDockerfile: &defaultDockerfile, - expectedBuildDir: &defaultBuildDirOnGenerate, - }, - { - generate: false, - requestedDockerfile: "foo.Dockerfile", - expectedOutDockerfile: &fooDockerfile, - expectedBuildDir: nil, - }, - { - generate: false, - requestedDockerfile: "", - expectedOutDockerfile: nil, - expectedBuildDir: nil, - }, - } - - for _, testCase := range cases { - actualBuildDir, actualOutDockerfile, actualCleanup, _ := buildContext( - testCase.generate, testCase.requestedDockerfile) - - if actualCleanup == nil { - // prevent regression - cleanup should never be nil - t.Fatal("buildContext returned nil cleanup function") - } - defer actualCleanup() - - if testCase.expectedOutDockerfile != nil && actualOutDockerfile != *testCase.expectedOutDockerfile { - t.Fatalf("comparing outDockerfile: expected %v actual %v", - *testCase.expectedOutDockerfile, - actualOutDockerfile) - } - - if testCase.expectedBuildDir != nil && actualBuildDir != *testCase.expectedBuildDir { - t.Fatalf("comparing buildDir: expected %v actual %v", - *testCase.expectedBuildDir, - actualBuildDir) - } - } -} diff --git a/pkg/lib/indexer/indexerfakes/fake_index_adder.go b/pkg/lib/indexer/indexerfakes/fake_index_adder.go deleted file mode 100644 index 8e9a3b1a7..000000000 --- a/pkg/lib/indexer/indexerfakes/fake_index_adder.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package indexerfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/lib/indexer" -) - -type FakeIndexAdder struct { - AddToIndexStub func(indexer.AddToIndexRequest) error - addToIndexMutex sync.RWMutex - addToIndexArgsForCall []struct { - arg1 indexer.AddToIndexRequest - } - addToIndexReturns struct { - result1 error - } - addToIndexReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeIndexAdder) AddToIndex(arg1 indexer.AddToIndexRequest) error { - fake.addToIndexMutex.Lock() - ret, specificReturn := fake.addToIndexReturnsOnCall[len(fake.addToIndexArgsForCall)] - fake.addToIndexArgsForCall = append(fake.addToIndexArgsForCall, struct { - arg1 indexer.AddToIndexRequest - }{arg1}) - stub := fake.AddToIndexStub - fakeReturns := fake.addToIndexReturns - fake.recordInvocation("AddToIndex", []interface{}{arg1}) - fake.addToIndexMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeIndexAdder) AddToIndexCallCount() int { - fake.addToIndexMutex.RLock() - defer fake.addToIndexMutex.RUnlock() - return len(fake.addToIndexArgsForCall) -} - -func (fake *FakeIndexAdder) AddToIndexCalls(stub func(indexer.AddToIndexRequest) error) { - fake.addToIndexMutex.Lock() - defer fake.addToIndexMutex.Unlock() - fake.AddToIndexStub = stub -} - -func (fake *FakeIndexAdder) AddToIndexArgsForCall(i int) indexer.AddToIndexRequest { - fake.addToIndexMutex.RLock() - defer fake.addToIndexMutex.RUnlock() - argsForCall := fake.addToIndexArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeIndexAdder) AddToIndexReturns(result1 error) { - fake.addToIndexMutex.Lock() - defer fake.addToIndexMutex.Unlock() - fake.AddToIndexStub = nil - fake.addToIndexReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexAdder) AddToIndexReturnsOnCall(i int, result1 error) { - fake.addToIndexMutex.Lock() - defer fake.addToIndexMutex.Unlock() - fake.AddToIndexStub = nil - if fake.addToIndexReturnsOnCall == nil { - fake.addToIndexReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.addToIndexReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexAdder) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.addToIndexMutex.RLock() - defer fake.addToIndexMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeIndexAdder) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ indexer.IndexAdder = new(FakeIndexAdder) diff --git a/pkg/lib/indexer/indexerfakes/fake_index_deleter.go b/pkg/lib/indexer/indexerfakes/fake_index_deleter.go deleted file mode 100644 index 5d70b904d..000000000 --- a/pkg/lib/indexer/indexerfakes/fake_index_deleter.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package indexerfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/lib/indexer" -) - -type FakeIndexDeleter struct { - DeleteFromIndexStub func(indexer.DeleteFromIndexRequest) error - deleteFromIndexMutex sync.RWMutex - deleteFromIndexArgsForCall []struct { - arg1 indexer.DeleteFromIndexRequest - } - deleteFromIndexReturns struct { - result1 error - } - deleteFromIndexReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeIndexDeleter) DeleteFromIndex(arg1 indexer.DeleteFromIndexRequest) error { - fake.deleteFromIndexMutex.Lock() - ret, specificReturn := fake.deleteFromIndexReturnsOnCall[len(fake.deleteFromIndexArgsForCall)] - fake.deleteFromIndexArgsForCall = append(fake.deleteFromIndexArgsForCall, struct { - arg1 indexer.DeleteFromIndexRequest - }{arg1}) - stub := fake.DeleteFromIndexStub - fakeReturns := fake.deleteFromIndexReturns - fake.recordInvocation("DeleteFromIndex", []interface{}{arg1}) - fake.deleteFromIndexMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeIndexDeleter) DeleteFromIndexCallCount() int { - fake.deleteFromIndexMutex.RLock() - defer fake.deleteFromIndexMutex.RUnlock() - return len(fake.deleteFromIndexArgsForCall) -} - -func (fake *FakeIndexDeleter) DeleteFromIndexCalls(stub func(indexer.DeleteFromIndexRequest) error) { - fake.deleteFromIndexMutex.Lock() - defer fake.deleteFromIndexMutex.Unlock() - fake.DeleteFromIndexStub = stub -} - -func (fake *FakeIndexDeleter) DeleteFromIndexArgsForCall(i int) indexer.DeleteFromIndexRequest { - fake.deleteFromIndexMutex.RLock() - defer fake.deleteFromIndexMutex.RUnlock() - argsForCall := fake.deleteFromIndexArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeIndexDeleter) DeleteFromIndexReturns(result1 error) { - fake.deleteFromIndexMutex.Lock() - defer fake.deleteFromIndexMutex.Unlock() - fake.DeleteFromIndexStub = nil - fake.deleteFromIndexReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexDeleter) DeleteFromIndexReturnsOnCall(i int, result1 error) { - fake.deleteFromIndexMutex.Lock() - defer fake.deleteFromIndexMutex.Unlock() - fake.DeleteFromIndexStub = nil - if fake.deleteFromIndexReturnsOnCall == nil { - fake.deleteFromIndexReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.deleteFromIndexReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexDeleter) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.deleteFromIndexMutex.RLock() - defer fake.deleteFromIndexMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeIndexDeleter) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ indexer.IndexDeleter = new(FakeIndexDeleter) diff --git a/pkg/lib/indexer/indexerfakes/fake_index_exporter.go b/pkg/lib/indexer/indexerfakes/fake_index_exporter.go deleted file mode 100644 index ce1a3af8c..000000000 --- a/pkg/lib/indexer/indexerfakes/fake_index_exporter.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package indexerfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/lib/indexer" -) - -type FakeIndexExporter struct { - ExportFromIndexStub func(indexer.ExportFromIndexRequest) error - exportFromIndexMutex sync.RWMutex - exportFromIndexArgsForCall []struct { - arg1 indexer.ExportFromIndexRequest - } - exportFromIndexReturns struct { - result1 error - } - exportFromIndexReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeIndexExporter) ExportFromIndex(arg1 indexer.ExportFromIndexRequest) error { - fake.exportFromIndexMutex.Lock() - ret, specificReturn := fake.exportFromIndexReturnsOnCall[len(fake.exportFromIndexArgsForCall)] - fake.exportFromIndexArgsForCall = append(fake.exportFromIndexArgsForCall, struct { - arg1 indexer.ExportFromIndexRequest - }{arg1}) - stub := fake.ExportFromIndexStub - fakeReturns := fake.exportFromIndexReturns - fake.recordInvocation("ExportFromIndex", []interface{}{arg1}) - fake.exportFromIndexMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeIndexExporter) ExportFromIndexCallCount() int { - fake.exportFromIndexMutex.RLock() - defer fake.exportFromIndexMutex.RUnlock() - return len(fake.exportFromIndexArgsForCall) -} - -func (fake *FakeIndexExporter) ExportFromIndexCalls(stub func(indexer.ExportFromIndexRequest) error) { - fake.exportFromIndexMutex.Lock() - defer fake.exportFromIndexMutex.Unlock() - fake.ExportFromIndexStub = stub -} - -func (fake *FakeIndexExporter) ExportFromIndexArgsForCall(i int) indexer.ExportFromIndexRequest { - fake.exportFromIndexMutex.RLock() - defer fake.exportFromIndexMutex.RUnlock() - argsForCall := fake.exportFromIndexArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeIndexExporter) ExportFromIndexReturns(result1 error) { - fake.exportFromIndexMutex.Lock() - defer fake.exportFromIndexMutex.Unlock() - fake.ExportFromIndexStub = nil - fake.exportFromIndexReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexExporter) ExportFromIndexReturnsOnCall(i int, result1 error) { - fake.exportFromIndexMutex.Lock() - defer fake.exportFromIndexMutex.Unlock() - fake.ExportFromIndexStub = nil - if fake.exportFromIndexReturnsOnCall == nil { - fake.exportFromIndexReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.exportFromIndexReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeIndexExporter) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.exportFromIndexMutex.RLock() - defer fake.exportFromIndexMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeIndexExporter) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ indexer.IndexExporter = new(FakeIndexExporter) diff --git a/pkg/lib/indexer/interfaces.go b/pkg/lib/indexer/interfaces.go deleted file mode 100644 index 26fea581d..000000000 --- a/pkg/lib/indexer/interfaces.go +++ /dev/null @@ -1,119 +0,0 @@ -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate -package indexer - -import ( - "github.com/sirupsen/logrus" - - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/lib/registry" -) - -// IndexAdder allows the creation of index container images from scratch or -// based on previous index images -// -//counterfeiter:generate . IndexAdder -type IndexAdder interface { - AddToIndex(AddToIndexRequest) error -} - -// NewIndexAdder is a constructor that returns an IndexAdder -func NewIndexAdder(buildTool, pullTool containertools.ContainerTool, logger *logrus.Entry) IndexAdder { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(buildTool, logger), - LabelReader: containertools.NewLabelReader(pullTool, logger), - RegistryAdder: registry.NewRegistryAdder(logger), - BuildTool: buildTool, - PullTool: pullTool, - Logger: logger, - } -} - -// IndexDeleter takes indexes and deletes all references to an operator -// from them -// -//counterfeiter:generate . IndexDeleter -type IndexDeleter interface { - DeleteFromIndex(DeleteFromIndexRequest) error -} - -// NewIndexDeleter is a constructor that returns an IndexDeleter -func NewIndexDeleter(buildTool, pullTool containertools.ContainerTool, logger *logrus.Entry) IndexDeleter { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(buildTool, logger), - LabelReader: containertools.NewLabelReader(pullTool, logger), - RegistryDeleter: registry.NewRegistryDeleter(logger), - BuildTool: buildTool, - PullTool: pullTool, - Logger: logger, - } -} - -//counterfeiter:generate . IndexExporter -type IndexExporter interface { - ExportFromIndex(ExportFromIndexRequest) error -} - -// NewIndexExporter is a constructor that returns an IndexExporter -func NewIndexExporter(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexExporter { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(containerTool, logger), - LabelReader: containertools.NewLabelReader(containerTool, logger), - BuildTool: containerTool, - PullTool: containerTool, - Logger: logger, - } -} - -// IndexStrandedPruner prunes operators out of an index -type IndexStrandedPruner interface { - PruneStrandedFromIndex(PruneStrandedFromIndexRequest) error -} - -func NewIndexStrandedPruner(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexStrandedPruner { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(containerTool, logger), - LabelReader: containertools.NewLabelReader(containerTool, logger), - RegistryStrandedPruner: registry.NewRegistryStrandedPruner(logger), - BuildTool: containerTool, - PullTool: containerTool, - Logger: logger, - } -} - -// IndexPruner prunes operators out of an index -type IndexPruner interface { - PruneFromIndex(PruneFromIndexRequest) error -} - -func NewIndexPruner(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexPruner { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(containerTool, logger), - LabelReader: containertools.NewLabelReader(containerTool, logger), - RegistryPruner: registry.NewRegistryPruner(logger), - BuildTool: containerTool, - PullTool: containerTool, - Logger: logger, - } -} - -// IndexDeprecator prunes operators out of an index -type IndexDeprecator interface { - DeprecateFromIndex(DeprecateFromIndexRequest) error -} - -func NewIndexDeprecator(buildTool, pullTool containertools.ContainerTool, logger *logrus.Entry) IndexDeprecator { - return ImageIndexer{ - DockerfileGenerator: containertools.NewDockerfileGenerator(logger), - CommandRunner: containertools.NewCommandRunner(buildTool, logger), - LabelReader: containertools.NewLabelReader(pullTool, logger), - RegistryDeprecator: registry.NewRegistryDeprecator(logger), - BuildTool: buildTool, - PullTool: pullTool, - Logger: logger, - } -} diff --git a/pkg/lib/indexer/testdata/bundles.db b/pkg/lib/indexer/testdata/bundles.db deleted file mode 100644 index d8ea4c492..000000000 Binary files a/pkg/lib/indexer/testdata/bundles.db and /dev/null differ diff --git a/pkg/lib/indexer/testdata/package.yaml b/pkg/lib/indexer/testdata/package.yaml deleted file mode 100644 index b594fafd1..000000000 --- a/pkg/lib/indexer/testdata/package.yaml +++ /dev/null @@ -1,9 +0,0 @@ -packageName: etcd -channels: -- name: alpha - currentCSV: etcdoperator.v0.9.2 -- name: beta - currentCSV: etcdoperator.v0.9.0 -- name: stable - currentCSV: etcdoperator.v0.9.2 -defaultChannel: alpha diff --git a/pkg/lib/registry/interfaces.go b/pkg/lib/registry/interfaces.go deleted file mode 100644 index f392d16e3..000000000 --- a/pkg/lib/registry/interfaces.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate -package registry - -import ( - "github.com/sirupsen/logrus" -) - -//counterfeiter:generate . RegistryAdder -type RegistryAdder interface { - AddToRegistry(AddToRegistryRequest) error -} - -func NewRegistryAdder(logger *logrus.Entry) RegistryAdder { - return RegistryUpdater{ - Logger: logger, - } -} - -//counterfeiter:generate . RegistryDeleter -type RegistryDeleter interface { - DeleteFromRegistry(DeleteFromRegistryRequest) error -} - -func NewRegistryDeleter(logger *logrus.Entry) RegistryDeleter { - return RegistryUpdater{ - Logger: logger, - } -} - -type RegistryStrandedPruner interface { - PruneStrandedFromRegistry(PruneStrandedFromRegistryRequest) error -} - -func NewRegistryStrandedPruner(logger *logrus.Entry) RegistryStrandedPruner { - return RegistryUpdater{ - Logger: logger, - } -} - -type RegistryPruner interface { - PruneFromRegistry(PruneFromRegistryRequest) error -} - -func NewRegistryPruner(logger *logrus.Entry) RegistryPruner { - return RegistryUpdater{ - Logger: logger, - } -} - -type RegistryDeprecator interface { - DeprecateFromRegistry(DeprecateFromRegistryRequest) error -} - -func NewRegistryDeprecator(logger *logrus.Entry) RegistryDeprecator { - return RegistryUpdater{ - Logger: logger, - } -} diff --git a/pkg/lib/registry/registry.go b/pkg/lib/registry/registry.go deleted file mode 100644 index 69f5a38d8..000000000 --- a/pkg/lib/registry/registry.go +++ /dev/null @@ -1,460 +0,0 @@ -package registry - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/sirupsen/logrus" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" - "github.com/operator-framework/operator-registry/pkg/image/execregistry" - "github.com/operator-framework/operator-registry/pkg/lib/certs" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -type RegistryUpdater struct { - Logger *logrus.Entry -} - -type AddToRegistryRequest struct { - Permissive bool - SkipTLSVerify bool - PlainHTTP bool - CaFile string - InputDatabase string - Bundles []string - Mode registry.Mode - ContainerTool containertools.ContainerTool - Overwrite bool - EnableAlpha bool -} - -func (r RegistryUpdater) AddToRegistry(request AddToRegistryRequest) error { - db, err := sqlite.Open(request.InputDatabase) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewSQLLiteLoader(db, sqlite.WithEnableAlpha(request.EnableAlpha)) - if err != nil { - return err - } - - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - if err != nil { - return err - } - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - - // add custom ca certs to resolver - - var reg image.Registry - var rerr error - switch request.ContainerTool { - case containertools.NoneTool: - rootCAs, err := certs.RootCAs(request.CaFile) - if err != nil { - return fmt.Errorf("failed to get RootCAs: %v", err) - } - reg, rerr = containerdregistry.NewRegistry( - containerdregistry.SkipTLSVerify(request.SkipTLSVerify), - containerdregistry.WithPlainHTTP(request.PlainHTTP), - containerdregistry.WithRootCAs(rootCAs), - ) - case containertools.PodmanTool: - fallthrough - case containertools.DockerTool: - reg, rerr = execregistry.NewRegistry(request.ContainerTool, r.Logger, containertools.SkipTLS(request.PlainHTTP)) - } - if rerr != nil { - return rerr - } - defer func() { - if err := reg.Destroy(); err != nil { - r.Logger.WithError(err).Warn("error destroying local cache") - } - }() - - simpleRefs := make([]image.Reference, 0) - for _, ref := range request.Bundles { - simpleRefs = append(simpleRefs, image.SimpleReference(ref)) - } - - if err := populate(context.TODO(), dbLoader, graphLoader, dbQuerier, reg, simpleRefs, request.Mode, request.Overwrite); err != nil { - r.Logger.Debugf("unable to populate database: %s", err) - - if !request.Permissive { - r.Logger.WithError(err).Error("permissive mode disabled") - return err - } - r.Logger.WithError(err).Warn("permissive mode enabled") - } - - return nil -} - -func unpackImage(ctx context.Context, reg image.Registry, ref image.Reference) (image.Reference, string, func(), error) { - var errs []error - workingDir, err := os.MkdirTemp("./", "bundle_tmp") - if err != nil { - errs = append(errs, err) - } - - if err = reg.Pull(ctx, ref); err != nil { - errs = append(errs, err) - } - - if err = reg.Unpack(ctx, ref, workingDir); err != nil { - errs = append(errs, err) - } - - cleanup := func() { - if err := os.RemoveAll(workingDir); err != nil { - logrus.Error(err) - } - } - - if len(errs) > 0 { - return nil, "", cleanup, utilerrors.NewAggregate(errs) - } - return ref, workingDir, cleanup, nil -} - -func populate(ctx context.Context, loader registry.Load, graphLoader registry.GraphLoader, querier registry.Query, reg image.Registry, refs []image.Reference, mode registry.Mode, overwrite bool) error { - unpackedImageMap := make(map[image.Reference]string, 0) - overwrittenBundles := map[string][]string{} - // nolint:prealloc - var imagesToAdd []*registry.Bundle - for _, ref := range refs { - to, from, cleanup, err := unpackImage(ctx, reg, ref) - if err != nil { - return err - } - unpackedImageMap[to] = from - defer cleanup() - - img, err := registry.NewImageInput(to, from) - if err != nil { - return err - } - imagesToAdd = append(imagesToAdd, img.Bundle) - - if overwrite { - overwritten, err := querier.GetBundlePathIfExists(ctx, img.Bundle.Name) - if err != nil { - if errors.Is(err, registry.ErrBundleImageNotInDatabase) { - continue - } - return err - } - if overwritten == "" { - return fmt.Errorf("index add --overwrite-latest is only supported when using bundle images") - } - overwrittenBundles[img.Bundle.Package] = append(overwrittenBundles[img.Bundle.Package], img.Bundle.Name) - } - } - - populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwrittenBundles) - - if err := populator.Populate(mode); err != nil { - return err - } - return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, imagesToAdd) -} - -type DeleteFromRegistryRequest struct { - Permissive bool - InputDatabase string - Packages []string -} - -func (r RegistryUpdater) DeleteFromRegistry(request DeleteFromRegistryRequest) error { - db, err := sqlite.Open(request.InputDatabase) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewDeprecationAwareLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - for _, pkg := range request.Packages { - remover := sqlite.NewSQLRemoverForPackages(dbLoader, pkg) - if err := remover.Remove(); err != nil { - err = fmt.Errorf("error deleting packages from database: %s", err) - if !request.Permissive { - logrus.WithError(err).Fatal("permissive mode disabled") - return err - } - logrus.WithError(err).Warn("permissive mode enabled") - } - } - - // remove any stranded bundles from the database - // TODO: This is unnecessary if the db schema can prevent this orphaned data from existing - remover := sqlite.NewSQLStrandedBundleRemover(dbLoader) - if err := remover.Remove(); err != nil { - return fmt.Errorf("error removing stranded packages from database: %s", err) - } - - if _, err := db.Exec("VACUUM"); err != nil { - return err - } - return nil -} - -type PruneStrandedFromRegistryRequest struct { - InputDatabase string -} - -func (r RegistryUpdater) PruneStrandedFromRegistry(request PruneStrandedFromRegistryRequest) error { - db, err := sqlite.Open(request.InputDatabase) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - remover := sqlite.NewSQLStrandedBundleRemover(dbLoader) - if err := remover.Remove(); err != nil { - return fmt.Errorf("error removing stranded packages from database: %s", err) - } - - if _, err := db.Exec("VACUUM"); err != nil { - return err - } - return nil -} - -type PruneFromRegistryRequest struct { - Permissive bool - InputDatabase string - Packages []string -} - -func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) error { - db, err := sqlite.Open(request.InputDatabase) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewDeprecationAwareLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - // get all the packages - lister := sqlite.NewSQLLiteQuerierFromDb(db) - packages, err := lister.ListPackages(context.TODO()) - if err != nil { - return err - } - - // make it inexpensive to find packages - pkgMap := make(map[string]bool) - for _, pkg := range request.Packages { - pkgMap[pkg] = true - } - - // prune packages from registry - for _, pkg := range packages { - if _, found := pkgMap[pkg]; !found { - remover := sqlite.NewSQLRemoverForPackages(dbLoader, pkg) - if err := remover.Remove(); err != nil { - err = fmt.Errorf("error deleting packages from database: %s", err) - if !request.Permissive { - logrus.WithError(err).Fatal("permissive mode disabled") - return err - } - logrus.WithError(err).Warn("permissive mode enabled") - } - } - } - - if _, err := db.Exec("VACUUM"); err != nil { - return err - } - return nil -} - -type DeprecateFromRegistryRequest struct { - Permissive bool - InputDatabase string - Bundles []string - AllowPackageRemoval bool -} - -func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequest) error { - db, err := sqlite.Open(request.InputDatabase) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewDeprecationAwareLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return fmt.Errorf("unable to migrate database: %s", err) - } - - // Check if all bundlepaths are valid - var toDeprecate []string - - dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db) - - toDeprecate, _, err = checkForBundlePaths(dbQuerier, request.Bundles) - if err != nil { - if !request.Permissive { - r.Logger.WithError(err).Error("permissive mode disabled") - return err - } - r.Logger.WithError(err).Warn("permissive mode enabled") - } - - deprecator := sqlite.NewSQLDeprecatorForBundles(dbLoader, toDeprecate) - - // Check for deprecation of head of default channel. If deprecation request includes heads of all other channels, - // then remove the package entirely. Otherwise, deprecate provided bundles. This enables deprecating an entire package. - // By default deprecating the head of default channel is not permitted. - if request.AllowPackageRemoval { - packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, dbQuerier) - if err := packageDeprecator.MaybeRemovePackages(); err != nil { - r.Logger.Debugf("unable to deprecate package from database: %s", err) - if !request.Permissive { - r.Logger.WithError(err).Error("permissive mode disabled") - return err - } - r.Logger.WithError(err).Warn("permissive mode enabled") - } - } - - // Any bundles associated with removed packages are now removed from the list of bundles to deprecate. - if err := deprecator.Deprecate(); err != nil { - r.Logger.Debugf("unable to deprecate bundles from database: %s", err) - if !request.Permissive { - r.Logger.WithError(err).Error("permissive mode disabled") - return err - } - r.Logger.WithError(err).Warn("permissive mode enabled") - } - - if _, err := db.Exec("VACUUM"); err != nil { - return err - } - return nil -} - -// checkForBundlePaths verifies presence of a list of bundle paths in the registry. -func checkForBundlePaths(querier registry.GRPCQuery, bundlePaths []string) ([]string, []string, error) { - if len(bundlePaths) == 0 { - return bundlePaths, nil, nil - } - - registryBundles, err := querier.ListBundles(context.TODO()) - if err != nil { - return bundlePaths, nil, err - } - - if len(registryBundles) == 0 { - return nil, bundlePaths, nil - } - - registryBundlePaths := map[string]struct{}{} - for _, b := range registryBundles { - registryBundlePaths[b.BundlePath] = struct{}{} - } - - // nolint:prealloc - var found, missing []string - for _, b := range bundlePaths { - if _, ok := registryBundlePaths[b]; ok { - found = append(found, b) - continue - } - missing = append(missing, b) - } - if len(missing) > 0 { - return found, missing, fmt.Errorf("target bundlepaths for deprecation missing from registry: %v", missing) - } - return found, missing, nil -} - -// replaces mode selects highest version as channel head and -// prunes any bundles in the upgrade chain after the channel head. -// check for the presence of newly added bundles after a replaces-mode add. -func checkForBundles(_ context.Context, _ *sqlite.SQLQuerier, g registry.GraphLoader, required []*registry.Bundle) error { - var errs []error - for _, bundle := range required { - graph, err := g.Generate(bundle.Package) - if err != nil { - errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", bundle.Package, err)) - continue - } - - for _, channel := range bundle.Channels { - var foundImage bool - for next := []registry.BundleKey{graph.Channels[channel].Head}; len(next) > 0; next = next[1:] { - if next[0].BundlePath == bundle.BundleImage { - foundImage = true - break - } - for edge := range graph.Channels[channel].Nodes[next[0]] { - next = append(next, edge) - } - } - - if foundImage { - continue - } - - headSkips := make([]string, 0, len(graph.Channels[channel].Nodes[graph.Channels[channel].Head])) - for b := range graph.Channels[channel].Nodes[graph.Channels[channel].Head] { - headSkips = append(headSkips, b.CsvName) - } - errs = append(errs, fmt.Errorf("add prunes bundle %s (%s) from package %s, channel %s: this may be "+ - "due to incorrect channel head (%s, skips/replaces %v). "+ - "Be aware that the head of the channel %s where you are trying to add the %s is %s. "+ - "Upgrade graphs follows the Semantic Versioning 2.0.0 (https://semver.org/) which means that "+ - "is not possible add new versions lower then the head of the channel", - bundle.Name, - bundle.BundleImage, - bundle.Package, - channel, - graph.Channels[channel].Head.CsvName, - headSkips, - channel, - bundle.Name, - graph.Channels[channel].Head.CsvName)) - } - } - return utilerrors.NewAggregate(errs) -} diff --git a/pkg/lib/registry/registry_test.go b/pkg/lib/registry/registry_test.go deleted file mode 100644 index a10779325..000000000 --- a/pkg/lib/registry/registry_test.go +++ /dev/null @@ -1,716 +0,0 @@ -package registry - -import ( - "context" - "crypto/rand" - "database/sql" - "encoding/json" - "errors" - "fmt" - "io" - "math" - "math/big" - "os" - "path/filepath" - "testing" - "testing/fstest" - - "github.com/blang/semver/v4" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/alpha/property" - "github.com/operator-framework/operator-registry/pkg/cache" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func fakeBundlePathFromName(name string) string { - return fmt.Sprintf("%s-path", name) -} - -func newCache(t *testing.T, bundles []*model.Bundle) cache.Cache { - t.Helper() - pkgs := map[string]*model.Package{} - channels := map[string]map[string]*model.Channel{} - - for _, b := range bundles { - if len(b.Image) == 0 { - b.Image = fakeBundlePathFromName(b.Name) - } - channelName := b.Channel.Name - packageName := b.Package.Name - if _, ok := pkgs[packageName]; !ok { - pkgs[packageName] = &model.Package{ - Name: packageName, - } - channels[packageName] = map[string]*model.Channel{ - channelName: { - Package: pkgs[packageName], - Name: channelName, - Bundles: map[string]*model.Bundle{b.Name: b}, - }, - } - pkgs[packageName].Channels = channels[packageName] - pkgs[packageName].DefaultChannel = channels[packageName][channelName] - } - - if _, ok := channels[packageName][channelName]; !ok { - channels[packageName][channelName] = &model.Channel{ - Package: pkgs[packageName], - Name: channelName, - Bundles: map[string]*model.Bundle{b.Name: b}, - } - pkgs[packageName].Channels[channelName] = channels[packageName][channelName] - } - b.Package = pkgs[packageName] - b.Channel = channels[packageName][channelName] - var pkgPropertyFound bool - for _, p := range b.Properties { - if p.Type == property.TypePackage { - pkgPropertyFound = true - break - } - } - if !pkgPropertyFound { - pkgJSON, _ := json.Marshal(property.Package{ - PackageName: b.Package.Name, - Version: b.Version.String(), - }) - b.Properties = append(b.Properties, property.Property{ - Type: property.TypePackage, - Value: pkgJSON, - }) - } - } - fbc := declcfg.ConvertFromModel(pkgs) - - fbcDir := t.TempDir() - cacheDir := t.TempDir() - - fbcFile, err := os.Create(filepath.Join(fbcDir, "catalog.json")) - require.NoError(t, err) - require.NoError(t, declcfg.WriteJSON(fbc, fbcFile)) - require.NoError(t, fbcFile.Close()) - - reg, err := cache.New(cacheDir) - require.NoError(t, err) - - require.NoError(t, reg.Build(context.Background(), os.DirFS(fbcDir))) - require.NoError(t, reg.Load(context.Background())) - - return reg -} - -func TestCheckForBundlePaths(t *testing.T) { - type testResult struct { - err error - found []string - missing []string - } - - tests := []struct { - description string - querier registry.GRPCQuery - checkPaths []string - expected testResult - }{ - { - description: "BundleListPresent", - querier: newCache(t, []*model.Bundle{ - { - Package: &model.Package{Name: "pkg-0"}, - Channel: &model.Channel{Name: "stable"}, - Name: "csv-a", - Version: semver.MustParse("1.0.0"), - }, - { - Package: &model.Package{Name: "pkg-0"}, - Channel: &model.Channel{Name: "alpha"}, - Name: "csv-b", - Version: semver.MustParse("2.0.0"), - }, - }), - checkPaths: []string{ - fakeBundlePathFromName("csv-a"), - }, - expected: testResult{ - err: nil, - found: []string{fakeBundlePathFromName("csv-a")}, - missing: nil, - }, - }, - { - description: "BundleListPartiallyMissing", - querier: newCache(t, []*model.Bundle{ - { - Package: &model.Package{Name: "pkg-0"}, - Channel: &model.Channel{Name: "stable"}, - Name: "csv-a", - Version: semver.MustParse("1.0.0"), - }, - { - Package: &model.Package{Name: "pkg-0"}, - Channel: &model.Channel{Name: "alpha"}, - Name: "csv-b", - Version: semver.MustParse("2.0.0"), - }, - }), - checkPaths: []string{ - fakeBundlePathFromName("csv-a"), - fakeBundlePathFromName("missing"), - }, - expected: testResult{ - err: fmt.Errorf("target bundlepaths for deprecation missing from registry: %v", []string{fakeBundlePathFromName("missing")}), - found: []string{fakeBundlePathFromName("csv-a")}, - missing: []string{fakeBundlePathFromName("missing")}, - }, - }, - { - description: "EmptyRegistry", - querier: newCache(t, nil), - checkPaths: []string{ - fakeBundlePathFromName("missing"), - }, - expected: testResult{ - err: nil, - missing: []string{fakeBundlePathFromName("missing")}, - }, - }, - { - description: "EmptyDeprecateList", - querier: newCache(t, []*model.Bundle{ - { - Package: &model.Package{Name: "pkg-0"}, - Channel: &model.Channel{Name: "stable"}, - Name: "csv-a", - Version: semver.MustParse("1.0.0"), - }, - }), - checkPaths: []string{}, - expected: testResult{ - err: nil, - found: []string{}, - missing: nil, - }, - }, - { - description: "InvalidQuerier", - querier: registry.NewEmptyQuerier(), - checkPaths: []string{fakeBundlePathFromName("missing")}, - expected: testResult{ - err: errors.New("empty querier: cannot list bundles"), - found: []string{}, - missing: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - found, missing, err := checkForBundlePaths(tt.querier, tt.checkPaths) - if qc, ok := tt.querier.(io.Closer); ok { - defer qc.Close() - } - if tt.expected.err != nil { - require.EqualError(t, err, tt.expected.err.Error()) - return - } - require.NoError(t, err) - - require.Equal(t, tt.expected.found, found) - require.Equal(t, tt.expected.missing, missing) - }) - } -} - -func TestUnpackImage(t *testing.T) { - type testResult struct { - dstImage string - err error - } - tests := []struct { - description string - registryImages []string - srcImage image.Reference - expected testResult - }{ - { - description: "unpackFS", - registryImages: []string{"image"}, - srcImage: image.SimpleReference("image"), - expected: testResult{ - dstImage: "image", - err: nil, - }, - }, - { - description: "missingImage", - registryImages: []string{}, - srcImage: image.SimpleReference("missing"), - expected: testResult{ - dstImage: "", - err: errors.New("not found"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - images := map[image.Reference]*image.MockImage{} - for _, i := range tt.registryImages { - images[image.SimpleReference(i)] = &image.MockImage{ - FS: fstest.MapFS{}, - } - } - ref, _, cleanup, err := unpackImage(context.TODO(), &image.MockRegistry{RemoteImages: images}, tt.srcImage) - if cleanup != nil { - cleanup() - } - - if tt.expected.err != nil { - require.EqualError(t, err, tt.expected.err.Error()) - return - } - require.NoError(t, err) - require.EqualValues(t, ref, tt.expected.dstImage) - }) - } -} - -func CreateTestDB(t *testing.T) (*sql.DB, func()) { - r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - require.NoError(t, err) - dbName := fmt.Sprintf("test-%d.db", r) - - db, err := sqlite.Open(dbName) - require.NoError(t, err) - - return db, func() { - defer func() { - if err := os.Remove(dbName); err != nil { - t.Fatal(err) - } - }() - if err := db.Close(); err != nil { - t.Fatal(err) - } - } -} - -func newUnpackedTestBundle(dir, name string, csvSpec json.RawMessage, annotations registry.Annotations, overwrite bool) (string, func(), error) { - bundleDir := filepath.Join(dir, fmt.Sprintf("%s-%s", annotations.PackageName, name)) - cleanup := func() { - os.RemoveAll(bundleDir) - } - - if overwrite { - os.RemoveAll(bundleDir) - } - if err := os.Mkdir(bundleDir, 0755); err != nil { - return bundleDir, cleanup, err - } - if err := os.Mkdir(filepath.Join(bundleDir, bundle.ManifestsDir), 0755); err != nil { - return bundleDir, cleanup, err - } - if err := os.Mkdir(filepath.Join(bundleDir, bundle.MetadataDir), 0755); err != nil { - return bundleDir, cleanup, err - } - if len(csvSpec) == 0 { - csvSpec = json.RawMessage(`{}`) - } - - rawCSV, err := json.Marshal(registry.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: sqlite.ClusterServiceVersionKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: csvSpec, - }) - if err != nil { - return bundleDir, cleanup, err - } - - rawObj := unstructured.Unstructured{} - if err := json.Unmarshal(rawCSV, &rawObj); err != nil { - return bundleDir, cleanup, err - } - rawObj.SetCreationTimestamp(metav1.Time{}) - - jsonout, err := rawObj.MarshalJSON() - if err != nil { - return bundleDir, cleanup, err - } - out, err := yaml.JSONToYAML(jsonout) - if err != nil { - return bundleDir, cleanup, err - } - if err := os.WriteFile(filepath.Join(bundleDir, bundle.ManifestsDir, "csv.yaml"), out, 0600); err != nil { - return bundleDir, cleanup, err - } - - out, err = yaml.Marshal(registry.AnnotationsFile{Annotations: annotations}) - if err != nil { - return bundleDir, cleanup, err - } - if err := os.WriteFile(filepath.Join(bundleDir, bundle.MetadataDir, "annotations.yaml"), out, 0600); err != nil { - return bundleDir, cleanup, err - } - return bundleDir, cleanup, nil -} - -type bundleDir struct { - version string - csvSpec json.RawMessage - annotations registry.Annotations - isOverwrite bool -} - -func TestCheckForBundles(t *testing.T) { - type step struct { - bundles map[string]bundleDir - action int - expected []*registry.Bundle // For testing pruning after deprecation - wantErr error - } - const ( - actionAdd = iota - actionDeprecate - actionOverwrite - ) - tests := []struct { - description string - steps []step - init func() (*sql.DB, func()) - }{ - { - // 1.1.0 -> 1.2.0 ok channel 1 - // \-> 1.2.0-1 pruned channel 2 - description: "ErrorOnNewPrunedBundle", - steps: []step{ - { - bundles: map[string]bundleDir{ - "newPruned-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.1.0", - }, - }, - action: actionAdd, - }, - { - bundles: map[string]bundleDir{ - "newPruned-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"newPruned-1.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "stable", - }, - version: "1.2.0", - }, - "newPruned-1.2.0-1": { - csvSpec: json.RawMessage(`{"version":"1.2.0-1","replaces":"newPruned-1.2.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.2.0-1", - }, - }, - action: actionAdd, - wantErr: fmt.Errorf("add prunes bundle newPruned-1.2.0-1 (newPruned-1.2.0-1) from package testpkg, channel alpha: this may be due to incorrect channel head (newPruned-1.2.0, skips/replaces [newPruned-1.1.0]). Be aware that the head of the channel alpha where you are trying to add the newPruned-1.2.0-1 is newPruned-1.2.0. Upgrade graphs follows the Semantic Versioning 2.0.0 (https://semver.org/) which means that is not possible add new versions lower then the head of the channel"), - }, - }, - }, - { - description: "silentPruneForExistingBundle", - steps: []step{ - { - bundles: map[string]bundleDir{ - "silentPrune-1.0.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.0.0", - }, - "silentPrune-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"silentPrune-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.1.0", - }, - }, - action: actionAdd, - }, - { - bundles: map[string]bundleDir{ - "silentPrune-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"silentPrune-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "stable", - }, - version: "1.2.0", - }, - }, - action: actionAdd, - }, - }, - }, - { - // 1.0.0 <- 1.0.1 <- 1.0.1-1 <- 1.0.2 (head) - // No pruning despite chain being out of order for 1.0.1 <- 1.0.1-1 - description: "allowUnorderedWithMaxChannelHead", - steps: []step{ - { - bundles: map[string]bundleDir{ - "unorderedReplaces-1.0.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.0.0", - }, - "unorderedReplaces-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"unorderedReplaces-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable,alpha", - DefaultChannelName: "stable", - }, - version: "1.1.0", - }, - }, - action: actionAdd, - }, - { - bundles: map[string]bundleDir{ - "unorderedReplaces-1.1.0-1": { - csvSpec: json.RawMessage(`{"version":"1.1.0-1","replaces":"unorderedReplaces-1.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "stable", - }, - version: "1.1.0-1", - }, - "unorderedReplaces-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"unorderedReplaces-1.1.0-1"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "stable", - }, - version: "1.2.0", - }, - }, - action: actionAdd, - }, - }, - }, - { - // If a pruned bundle was deprecated, ignore - description: "withDeprecated", - steps: []step{ - { - bundles: map[string]bundleDir{ - "withDeprecated-1.0.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.0.0", - }, - "withDeprecated-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"withDeprecated-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.1.0", - }, - "withDeprecated-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"withDeprecated-1.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.2.0", - }, - }, - action: actionAdd, - }, - { - bundles: map[string]bundleDir{ - "withDeprecated-1.1.0": {}, - }, - action: actionDeprecate, - expected: []*registry.Bundle{ - { - Name: "withDeprecated-1.1.0", - Package: "testpkg", - Channels: []string{"stable"}, - BundleImage: "withDeprecated-1.1.0", - }, - { - Name: "withDeprecated-1.2.0", - Package: "testpkg", - Channels: []string{"stable"}, - BundleImage: "withDeprecated-1.2.0", - }, - }, - }, - { - bundles: map[string]bundleDir{ - "withDeprecated-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":""}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "alpha", - }, - version: "1.2.0", - isOverwrite: true, - }, - }, - action: actionOverwrite, - }, - }, - }, - { - // bundle version should be immutable anyway, but only csv name is required to stay unchanged in overwrite - description: "overwritePruning", - steps: []step{ - { - bundles: map[string]bundleDir{ - "withOverwrite-1.0.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.0.0", - }, - "withOverwrite-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"withOverwrite-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.1.0", - }, - }, - action: actionAdd, - }, - { - bundles: map[string]bundleDir{ - "withOverwrite-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0-1","replaces":"withOverwrite-1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "stable", - }, - version: "1.0.0-1", - isOverwrite: true, - }, - "withOverwrite-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"withOverwrite-1.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg", - Channels: "alpha", - DefaultChannelName: "stable", - }, - version: "1.2.0", - }, - }, - action: actionOverwrite, - wantErr: fmt.Errorf("add prunes bundle withOverwrite-1.1.0 (withOverwrite-1.1.0-overwrite) from package testpkg, channel stable: this may be due to incorrect channel head (withOverwrite-1.0.0, skips/replaces []). Be aware that the head of the channel stable where you are trying to add the withOverwrite-1.1.0 is withOverwrite-1.0.0. Upgrade graphs follows the Semantic Versioning 2.0.0 (https://semver.org/) which means that is not possible add new versions lower then the head of the channel"), - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - tmpdir := t.TempDir() - db, cleanup := CreateTestDB(t) - defer cleanup() - load, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, load.Migrate(context.TODO())) - query := sqlite.NewSQLLiteQuerierFromDb(db) - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - for _, step := range tt.steps { - expected := []*registry.Bundle{} - switch step.action { - case actionDeprecate: - for deprecate := range step.bundles { - require.NoError(t, load.DeprecateBundle(deprecate)) - } - expected = step.expected - case actionAdd, actionOverwrite: - overwriteRefs := map[string][]string{} - refs := map[image.Reference]string{} - for name, b := range step.bundles { - dir, _, err := newUnpackedTestBundle(tmpdir, name, b.csvSpec, b.annotations, true) - require.NoError(t, err) - - // refs to be added - bundleImage := name - - // bundles to remove for overwrite. Only one per package is permitted. - if step.action == actionOverwrite && b.isOverwrite { - bundleImage += "-overwrite" - } - - img, err := registry.NewImageInput(image.SimpleReference(bundleImage), dir) - require.NoError(t, err) - expected = append(expected, img.Bundle) - - if step.action == actionOverwrite && b.isOverwrite { - overwriteRefs[img.Bundle.Package] = append(overwriteRefs[img.Bundle.Package], name) - } - refs[image.SimpleReference(bundleImage)] = dir - } - require.NoError(t, registry.NewDirectoryPopulator( - load, - graphLoader, - query, - refs, - overwriteRefs).Populate(registry.ReplacesMode)) - } - err = checkForBundles(context.TODO(), query, graphLoader, expected) - if step.wantErr == nil { - // nolint:testifylint - require.NoError(t, err, fmt.Sprintf("%d", step.action)) - continue - } - require.EqualError(t, err, step.wantErr.Error()) - } - }) - } -} diff --git a/pkg/lib/registry/registryfakes/fake_registry_adder.go b/pkg/lib/registry/registryfakes/fake_registry_adder.go deleted file mode 100644 index 5c166adb7..000000000 --- a/pkg/lib/registry/registryfakes/fake_registry_adder.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package registryfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" -) - -type FakeRegistryAdder struct { - AddToRegistryStub func(registry.AddToRegistryRequest) error - addToRegistryMutex sync.RWMutex - addToRegistryArgsForCall []struct { - arg1 registry.AddToRegistryRequest - } - addToRegistryReturns struct { - result1 error - } - addToRegistryReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeRegistryAdder) AddToRegistry(arg1 registry.AddToRegistryRequest) error { - fake.addToRegistryMutex.Lock() - ret, specificReturn := fake.addToRegistryReturnsOnCall[len(fake.addToRegistryArgsForCall)] - fake.addToRegistryArgsForCall = append(fake.addToRegistryArgsForCall, struct { - arg1 registry.AddToRegistryRequest - }{arg1}) - stub := fake.AddToRegistryStub - fakeReturns := fake.addToRegistryReturns - fake.recordInvocation("AddToRegistry", []interface{}{arg1}) - fake.addToRegistryMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeRegistryAdder) AddToRegistryCallCount() int { - fake.addToRegistryMutex.RLock() - defer fake.addToRegistryMutex.RUnlock() - return len(fake.addToRegistryArgsForCall) -} - -func (fake *FakeRegistryAdder) AddToRegistryCalls(stub func(registry.AddToRegistryRequest) error) { - fake.addToRegistryMutex.Lock() - defer fake.addToRegistryMutex.Unlock() - fake.AddToRegistryStub = stub -} - -func (fake *FakeRegistryAdder) AddToRegistryArgsForCall(i int) registry.AddToRegistryRequest { - fake.addToRegistryMutex.RLock() - defer fake.addToRegistryMutex.RUnlock() - argsForCall := fake.addToRegistryArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeRegistryAdder) AddToRegistryReturns(result1 error) { - fake.addToRegistryMutex.Lock() - defer fake.addToRegistryMutex.Unlock() - fake.AddToRegistryStub = nil - fake.addToRegistryReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeRegistryAdder) AddToRegistryReturnsOnCall(i int, result1 error) { - fake.addToRegistryMutex.Lock() - defer fake.addToRegistryMutex.Unlock() - fake.AddToRegistryStub = nil - if fake.addToRegistryReturnsOnCall == nil { - fake.addToRegistryReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.addToRegistryReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeRegistryAdder) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.addToRegistryMutex.RLock() - defer fake.addToRegistryMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeRegistryAdder) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ registry.RegistryAdder = new(FakeRegistryAdder) diff --git a/pkg/lib/registry/registryfakes/fake_registry_deleter.go b/pkg/lib/registry/registryfakes/fake_registry_deleter.go deleted file mode 100644 index 340346a0f..000000000 --- a/pkg/lib/registry/registryfakes/fake_registry_deleter.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package registryfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/lib/registry" -) - -type FakeRegistryDeleter struct { - DeleteFromRegistryStub func(registry.DeleteFromRegistryRequest) error - deleteFromRegistryMutex sync.RWMutex - deleteFromRegistryArgsForCall []struct { - arg1 registry.DeleteFromRegistryRequest - } - deleteFromRegistryReturns struct { - result1 error - } - deleteFromRegistryReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistry(arg1 registry.DeleteFromRegistryRequest) error { - fake.deleteFromRegistryMutex.Lock() - ret, specificReturn := fake.deleteFromRegistryReturnsOnCall[len(fake.deleteFromRegistryArgsForCall)] - fake.deleteFromRegistryArgsForCall = append(fake.deleteFromRegistryArgsForCall, struct { - arg1 registry.DeleteFromRegistryRequest - }{arg1}) - stub := fake.DeleteFromRegistryStub - fakeReturns := fake.deleteFromRegistryReturns - fake.recordInvocation("DeleteFromRegistry", []interface{}{arg1}) - fake.deleteFromRegistryMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistryCallCount() int { - fake.deleteFromRegistryMutex.RLock() - defer fake.deleteFromRegistryMutex.RUnlock() - return len(fake.deleteFromRegistryArgsForCall) -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistryCalls(stub func(registry.DeleteFromRegistryRequest) error) { - fake.deleteFromRegistryMutex.Lock() - defer fake.deleteFromRegistryMutex.Unlock() - fake.DeleteFromRegistryStub = stub -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistryArgsForCall(i int) registry.DeleteFromRegistryRequest { - fake.deleteFromRegistryMutex.RLock() - defer fake.deleteFromRegistryMutex.RUnlock() - argsForCall := fake.deleteFromRegistryArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistryReturns(result1 error) { - fake.deleteFromRegistryMutex.Lock() - defer fake.deleteFromRegistryMutex.Unlock() - fake.DeleteFromRegistryStub = nil - fake.deleteFromRegistryReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeRegistryDeleter) DeleteFromRegistryReturnsOnCall(i int, result1 error) { - fake.deleteFromRegistryMutex.Lock() - defer fake.deleteFromRegistryMutex.Unlock() - fake.DeleteFromRegistryStub = nil - if fake.deleteFromRegistryReturnsOnCall == nil { - fake.deleteFromRegistryReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.deleteFromRegistryReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeRegistryDeleter) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.deleteFromRegistryMutex.RLock() - defer fake.deleteFromRegistryMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeRegistryDeleter) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ registry.RegistryDeleter = new(FakeRegistryDeleter) diff --git a/pkg/mirror/mirror.go b/pkg/mirror/mirror.go deleted file mode 100644 index 7d539aa1e..000000000 --- a/pkg/mirror/mirror.go +++ /dev/null @@ -1,111 +0,0 @@ -package mirror - -import ( - "context" - "fmt" - "strings" - - "github.com/distribution/reference" - "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -type Mirrorer interface { - Mirror() (map[string]string, error) -} - -// DatabaseExtractor knows how to pull an index image and extract its database -type DatabaseExtractor interface { - Extract(from string) (string, error) -} - -type DatabaseExtractorFunc func(from string) (string, error) - -func (f DatabaseExtractorFunc) Extract(from string) (string, error) { - return f(from) -} - -// ImageMirrorer knows how to mirror an image from one registry to another -type ImageMirrorer interface { - Mirror(mapping map[string]string) error -} - -type ImageMirrorerFunc func(mapping map[string]string) error - -func (f ImageMirrorerFunc) Mirror(mapping map[string]string) error { - return f(mapping) -} - -type IndexImageMirrorer struct { - ImageMirrorer ImageMirrorer - DatabaseExtractor DatabaseExtractor - - // options - Source, Dest string -} - -var _ Mirrorer = &IndexImageMirrorer{} - -func NewIndexImageMirror(options ...ImageIndexMirrorOption) (*IndexImageMirrorer, error) { - config := DefaultImageIndexMirrorerOptions() - config.Apply(options) - if err := config.Complete(); err != nil { - return nil, err - } - if err := config.Validate(); err != nil { - return nil, err - } - return &IndexImageMirrorer{ - ImageMirrorer: config.ImageMirrorer, - DatabaseExtractor: config.DatabaseExtractor, - Source: config.Source, - Dest: config.Dest, - }, nil -} - -func (b *IndexImageMirrorer) Mirror() (map[string]string, error) { - dbPath, err := b.DatabaseExtractor.Extract(b.Source) - if err != nil { - return nil, err - } - - db, err := sqlite.Open(dbPath) - if err != nil { - return nil, err - } - defer db.Close() - - migrator, err := sqlite.NewSQLLiteMigrator(db) - if err != nil { - return nil, err - } - if err := migrator.Migrate(context.TODO()); err != nil { - return nil, err - } - - querier := sqlite.NewSQLLiteQuerierFromDb(db) - images, err := querier.ListImages(context.TODO()) - if err != nil { - return nil, err - } - - mapping := map[string]string{} - - var errs []error - for _, img := range images { - ref, err := reference.ParseNormalizedNamed(img) - if err != nil { - errs = append(errs, fmt.Errorf("couldn't parse image for mirroring (%s), skipping mirror: %s", img, err.Error())) - continue - } - domain := reference.Domain(ref) - mapping[ref.String()] = b.Dest + strings.TrimPrefix(ref.String(), domain) - } - - if err := b.ImageMirrorer.Mirror(mapping); err != nil { - errs = append(errs, fmt.Errorf("mirroring failed: %s", err.Error())) - } - - return mapping, errors.NewAggregate(errs) -} diff --git a/pkg/mirror/mirror_test.go b/pkg/mirror/mirror_test.go deleted file mode 100644 index e5de413c1..000000000 --- a/pkg/mirror/mirror_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package mirror - -import ( - "context" - "crypto/rand" - "database/sql" - "fmt" - "math" - "math/big" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func CreateTestDB(t *testing.T) (*sql.DB, string, func()) { - r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - require.NoError(t, err) - dbName := fmt.Sprintf("test-%d.db", r.Int64()) - - db, err := sqlite.Open(dbName) - require.NoError(t, err) - - load, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, load.Migrate(context.TODO())) - - loader := sqlite.NewSQLLoaderForDirectory(load, "../../manifests") - require.NoError(t, loader.Populate()) - - return db, dbName, func() { - defer func() { - if err := os.Remove(dbName); err != nil { - t.Fatal(err) - } - }() - if err := db.Close(); err != nil { - t.Fatal(err) - } - } -} - -func TestIndexImageMirrorer_Mirror(t *testing.T) { - _, path, cleanup := CreateTestDB(t) - defer cleanup() - - var testExtractor DatabaseExtractorFunc = func(from string) (string, error) { - return path, nil - } - type fields struct { - ImageMirrorer ImageMirrorerFunc - DatabaseExtractor DatabaseExtractorFunc - Source string - Dest string - } - tests := []struct { - name string - fields fields - want map[string]string - wantErr error - }{ - { - name: "mirror images", - fields: fields{ - DatabaseExtractor: testExtractor, - Source: "example", - Dest: "localhost", - }, - want: map[string]string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943": "localhost/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": "localhost/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - "quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8": "localhost/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8", - "quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84": "localhost/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84", - "quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f": "localhost/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f", - "quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d": "localhost/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d", - "quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf": "localhost/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf", - "quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731": "localhost/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731", - "docker.io/strimzi/cluster-operator:0.11.0": "localhost/strimzi/cluster-operator:0.11.0", - "docker.io/strimzi/cluster-operator:0.11.1": "localhost/strimzi/cluster-operator:0.11.1", - "docker.io/strimzi/operator:0.12.1": "localhost/strimzi/operator:0.12.1", - "docker.io/strimzi/operator:0.12.2": "localhost/strimzi/operator:0.12.2", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.fields.ImageMirrorer == nil { - tt.fields.ImageMirrorer = func(mapping map[string]string) error { - require.Equal(t, tt.want, mapping) - return nil - } - } - - b := &IndexImageMirrorer{ - ImageMirrorer: tt.fields.ImageMirrorer, - DatabaseExtractor: tt.fields.DatabaseExtractor, - Source: tt.fields.Source, - Dest: tt.fields.Dest, - } - got, err := b.Mirror() - if err != nil { - require.Equal(t, tt.wantErr.Error(), err.Error()) - } - require.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/mirror/options.go b/pkg/mirror/options.go deleted file mode 100644 index 51c004faa..000000000 --- a/pkg/mirror/options.go +++ /dev/null @@ -1,111 +0,0 @@ -package mirror - -import ( - "fmt" -) - -type IndexImageMirrorerOptions struct { - ImageMirrorer ImageMirrorer - DatabaseExtractor DatabaseExtractor - - Source, Dest string - ManifestDir string -} - -func (o *IndexImageMirrorerOptions) Validate() error { - // TODO: better validation - - if o.ImageMirrorer == nil { - return fmt.Errorf("can't mirror without a mirrorer configured") - } - if o.DatabaseExtractor == nil { - return fmt.Errorf("can't mirror without a database extractor configured") - } - if o.Source == "" { - return fmt.Errorf("source image required") - } - - if o.Dest == "" { - return fmt.Errorf("destination registry required") - } - - if o.ManifestDir == "" { - return fmt.Errorf("must have directory to write manifests to") - } - - return nil -} - -func (o *IndexImageMirrorerOptions) Complete() error { - if o.ManifestDir == "" { - o.ManifestDir = "./manifests" - } - return nil -} - -// Apply sequentially applies the given options to the config. -func (o *IndexImageMirrorerOptions) Apply(options []ImageIndexMirrorOption) { - for _, option := range options { - option(o) - } -} - -// ToOption converts an IndexImageMirrorerOptions object into a function that applies -// its current configuration to another IndexImageMirrorerOptions instance -func (o *IndexImageMirrorerOptions) ToOption() ImageIndexMirrorOption { - return func(io *IndexImageMirrorerOptions) { - if o.ImageMirrorer != nil { - io.ImageMirrorer = o.ImageMirrorer - } - if o.DatabaseExtractor != nil { - io.DatabaseExtractor = o.DatabaseExtractor - } - if o.Source != "" { - io.Source = o.Source - } - if o.Dest != "" { - io.Dest = o.Dest - } - if o.ManifestDir != "" { - io.ManifestDir = o.ManifestDir - } - } -} - -type ImageIndexMirrorOption func(*IndexImageMirrorerOptions) - -func DefaultImageIndexMirrorerOptions() *IndexImageMirrorerOptions { - return &IndexImageMirrorerOptions{ - ManifestDir: "./manifests", - } -} - -func WithMirrorer(i ImageMirrorer) ImageIndexMirrorOption { - return func(o *IndexImageMirrorerOptions) { - o.ImageMirrorer = i - } -} - -func WithExtractor(e DatabaseExtractor) ImageIndexMirrorOption { - return func(o *IndexImageMirrorerOptions) { - o.DatabaseExtractor = e - } -} - -func WithSource(s string) ImageIndexMirrorOption { - return func(o *IndexImageMirrorerOptions) { - o.Source = s - } -} - -func WithDest(d string) ImageIndexMirrorOption { - return func(o *IndexImageMirrorerOptions) { - o.Dest = d - } -} - -func WithManifestDir(d string) ImageIndexMirrorOption { - return func(o *IndexImageMirrorerOptions) { - o.ManifestDir = d - } -} diff --git a/pkg/registry/populator_test.go b/pkg/registry/populator_test.go deleted file mode 100644 index e18f4cfe8..000000000 --- a/pkg/registry/populator_test.go +++ /dev/null @@ -1,3174 +0,0 @@ -package registry_test - -import ( - "context" - "crypto/rand" - "database/sql" - "encoding/json" - "errors" - "fmt" - "math" - "math/big" - "os" - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/blang/semver/v4" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/api/pkg/lib/version" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -func CreateTestDB(t *testing.T) (*sql.DB, func()) { - r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - require.NoError(t, err) - dbName := fmt.Sprintf("test-%d.db", r.Int64()) - - db, err := sqlite.Open(dbName) - require.NoError(t, err) - - return db, func() { - defer func() { - if err := os.Remove(dbName); err != nil { - t.Fatal(err) - } - }() - if err := db.Close(); err != nil { - t.Fatal(err) - } - } -} - -func createAndPopulateDB(db *sql.DB) (*sqlite.SQLQuerier, error) { - load, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return nil, err - } - err = load.Migrate(context.TODO()) - if err != nil { - return nil, err - } - query := sqlite.NewSQLLiteQuerierFromDb(db) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - if err != nil { - return nil, err - } - - populate := func(names []string) error { - refMap := make(map[image.Reference]string, 0) - for _, name := range names { - refMap[image.SimpleReference("quay.io/test/"+name)] = "../../bundles/" + name - } - return registry.NewDirectoryPopulator( - load, - graphLoader, - query, - refMap, - nil).Populate(registry.ReplacesMode) - } - names := []string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.22.2", "prometheus.0.14.0", "prometheus.0.15.0"} - if err := populate(names); err != nil { - return nil, err - } - - return query, nil -} - -func TestImageLoader(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - _, err := createAndPopulateDB(db) - require.NoError(t, err) -} - -func TestQuerierForImage(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - store, err := createAndPopulateDB(db) - require.NoError(t, err) - - foundPackages, err := store.ListPackages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{"etcd", "prometheus"}, foundPackages) - - etcdPackage, err := store.GetPackage(context.TODO(), "etcd") - require.NoError(t, err) - require.Equal(t, ®istry.PackageManifest{ - PackageName: "etcd", - DefaultChannelName: "alpha", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "etcdoperator.v0.9.2", - }, - { - Name: "beta", - CurrentCSVName: "etcdoperator.v0.9.0", - }, - { - Name: "stable", - CurrentCSVName: "etcdoperator.v0.9.2", - }, - }, - }, etcdPackage) - - etcdBundleByChannel, err := store.GetBundleForChannel(context.TODO(), "etcd", "alpha") - require.NoError(t, err) - expectedBundle := &api.Bundle{ - CsvName: "etcdoperator.v0.9.2", - PackageName: "etcd", - ChannelName: "alpha", - 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\\\"}}}]\",\"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 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\"}}", - 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\\\"}}}]\",\"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 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\"}}", "{\"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: "quay.io/test/etcd.0.9.2", - Version: "0.9.2", - SkipRange: "", - Dependencies: []*api.Dependency{ - { - Type: "olm.gvk", - Value: `{"group":"testapi.coreos.com","kind":"testapi","version":"v1"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.constraint", - Value: `{"cel":{"rule":"properties.exists(p, p.type == \"certified\")"},"failureMessage":"require to have \"certified\""}`, - }, - }, - Properties: []*api.Property{ - { - Type: "olm.package", - Value: `{"packageName":"etcd","version":"0.9.2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdBackup","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdRestore","version":"v1beta2"}`, - }, - }, - ProvidedApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdBackup", Plural: "etcdbackups"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdRestore", Plural: "etcdrestores"}, - }, - RequiredApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - {Group: "testapi.coreos.com", Version: "v1", Kind: "testapi"}, - }, - } - bareGetBundleForChannelResult := &api.Bundle{ - CsvName: expectedBundle.CsvName, - CsvJson: expectedBundle.CsvJson + "\n", - } - EqualBundles(t, *bareGetBundleForChannelResult, *etcdBundleByChannel) - - etcdBundle, err := store.GetBundle(context.TODO(), "etcd", "alpha", "etcdoperator.v0.9.2") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundle) - - etcdChannelEntries, err := store.GetChannelEntriesThatReplace(context.TODO(), "etcdoperator.v0.9.0") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdChannelEntries) - - etcdBundleByReplaces, err := store.GetBundleThatReplaces(context.TODO(), "etcdoperator.v0.9.0", "etcd", "alpha") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByReplaces) - - etcdChannelEntriesThatProvide, err := store.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{ - {"etcd", "alpha", "etcdoperator.v0.9.0", ""}, - {"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.1"}, - {"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "stable", "etcdoperator.v0.9.0", ""}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.1"}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "beta", "etcdoperator.v0.9.0", ""}}, etcdChannelEntriesThatProvide) - - etcdLatestChannelEntriesThatProvide, err := store.GetLatestChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "beta", "etcdoperator.v0.9.0", ""}}, etcdLatestChannelEntriesThatProvide) - - etcdBundleByProvides, err := store.GetBundleThatProvides(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByProvides) - - expectedEtcdImages := []string{ - "quay.io/test/etcd.0.9.2", - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - } - etcdImages, err := store.GetImagesForBundle(context.TODO(), "etcdoperator.v0.9.2") - require.NoError(t, err) - require.ElementsMatch(t, expectedEtcdImages, etcdImages) - - expectedDatabaseImages := []string{ - "quay.io/test/etcd.0.9.0", - "quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8", - "quay.io/test/etcd.0.9.2", - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - "quay.io/test/prometheus.0.14.0", - "quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731", - "quay.io/test/prometheus.0.15.0", - "quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d", - "quay.io/test/prometheus.0.22.2", - "quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf", - } - dbImages, err := store.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, expectedDatabaseImages, dbImages) - - version, err := store.GetBundleVersion(context.TODO(), "quay.io/test/etcd.0.9.2") - require.NoError(t, err) - require.Equal(t, "0.9.2", version) - - bundlePaths, err := store.GetBundlePathsForPackage(context.TODO(), "etcd") - require.NoError(t, err) - expectedBundlePaths := []string{"quay.io/test/etcd.0.9.0", "quay.io/test/etcd.0.9.2"} - require.ElementsMatch(t, expectedBundlePaths, bundlePaths) - - defaultChannel, err := store.GetDefaultChannelForPackage(context.TODO(), "etcd") - require.NoError(t, err) - require.Equal(t, "alpha", defaultChannel) - - listChannels, err := store.ListChannels(context.TODO(), "etcd") - require.NoError(t, err) - expectedListChannels := []string{"alpha", "stable", "beta"} - require.ElementsMatch(t, expectedListChannels, listChannels) - - currentCSVName, err := store.GetCurrentCSVNameForChannel(context.TODO(), "etcd", "alpha") - require.NoError(t, err) - require.Equal(t, "etcdoperator.v0.9.2", currentCSVName) -} - -func TestImageLoading(t *testing.T) { - // TODO: remove requirement to have real files - type img struct { - ref image.SimpleReference - dir string - } - tests := []struct { - name string - initImages []img - addImage img - wantPackages []*registry.Package - wantErr bool - err error - }{ - { - name: "OneChannel/AddBundleToTwoChannels", - initImages: []img{ - { - // this is in the "preview" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - }, - addImage: img{ - // this is in the "preview" and "stable" channels and replaces 0.14.0 - ref: image.SimpleReference("quay.io/prometheus/operator:0.15.0"), - dir: "../../bundles/prometheus.0.15.0", - }, - wantPackages: []*registry.Package{ - { - Name: "prometheus", - DefaultChannel: "preview", - Channels: map[string]registry.Channel{ - "preview": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.15.0", - Version: "0.15.0", - CsvName: "prometheusoperator.0.15.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.15.0", Version: "0.15.0", CsvName: "prometheusoperator.0.15.0"}: { - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: struct{}{}, - }, - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}, - }, - }, - "stable": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.15.0", - Version: "0.15.0", - CsvName: "prometheusoperator.0.15.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.15.0", Version: "0.15.0", CsvName: "prometheusoperator.0.15.0"}: { - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: struct{}{}, - }, - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}}, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "OneChannel/AddBundleToNewChannel", - initImages: []img{ - { - // this is in the "preview" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - }, - addImage: img{ - // this is in the "beta" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0-beta"), - dir: "../../bundles/prometheus.0.14.0-beta", - }, - wantPackages: []*registry.Package{ - { - Name: "prometheus", - DefaultChannel: "preview", - Channels: map[string]registry.Channel{ - "preview": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.14.0", - Version: "0.14.0", - CsvName: "prometheusoperator.0.14.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}, - }, - }, - "beta": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.14.0-beta", - Version: "0.14.0-beta", - CsvName: "prometheusoperator.0.14.0-beta", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.14.0-beta", Version: "0.14.0-beta", CsvName: "prometheusoperator.0.14.0-beta"}: {}, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "TwoChannel/OneChannelIsASubset", - initImages: []img{ - { - // this is in the "preview" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - }, - addImage: img{ - // this is in the "stable" channel and replaces v0.14.0 - ref: image.SimpleReference("quay.io/prometheus/operator:0.15.0-stable"), - dir: "../../bundles/prometheus.0.15.0-stable", - }, - wantPackages: []*registry.Package{ - { - Name: "prometheus", - DefaultChannel: "stable", - Channels: map[string]registry.Channel{ - "preview": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.14.0", - Version: "0.14.0", - CsvName: "prometheusoperator.0.14.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}, - }, - }, - "stable": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.15.0-stable", - Version: "0.15.0", - CsvName: "prometheusoperator.0.15.0-stable", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.15.0-stable", Version: "0.15.0", CsvName: "prometheusoperator.0.15.0-stable"}: { - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: struct{}{}, - }, - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}}, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "AddBundleAlreadyExists", - initImages: []img{ - { - // this is in the "preview" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - }, - addImage: img{ - //Adding same bundle different bundle - ref: image.SimpleReference("quay.io/prometheus/operator-test:testing"), - dir: "../../bundles/prometheus.0.14.0", - }, - wantPackages: []*registry.Package{ - { - Name: "prometheus", - DefaultChannel: "stable", - Channels: map[string]registry.Channel{ - "preview": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.14.0", - Version: "0.14.0", - CsvName: "prometheusoperator.0.14.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}, - }, - }, - }, - }, - }, - wantErr: true, - err: registry.PackageVersionAlreadyAddedErr{}, - }, - { - name: "AddExactBundleAlreadyExists", - initImages: []img{ - { - // this is in the "preview" channel - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - }, - addImage: img{ - // Add the same package - ref: image.SimpleReference("quay.io/prometheus/operator:0.14.0"), - dir: "../../bundles/prometheus.0.14.0", - }, - wantPackages: []*registry.Package{ - { - Name: "prometheus", - DefaultChannel: "stable", - Channels: map[string]registry.Channel{ - "preview": { - Head: registry.BundleKey{ - BundlePath: "quay.io/prometheus/operator:0.14.0", - Version: "0.14.0", - CsvName: "prometheusoperator.0.14.0", - }, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "quay.io/prometheus/operator:0.14.0", Version: "0.14.0", CsvName: "prometheusoperator.0.14.0"}: {}, - }, - }, - }, - }, - }, - wantErr: true, - err: registry.BundleImageAlreadyAddedErr{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - load, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, load.Migrate(context.TODO())) - query := sqlite.NewSQLLiteQuerierFromDb(db) - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - for _, i := range tt.initImages { - p := registry.NewDirectoryPopulator( - load, - graphLoader, - query, - map[image.Reference]string{i.ref: i.dir}, - nil) - require.NoError(t, p.Populate(registry.ReplacesMode)) - } - add := registry.NewDirectoryPopulator( - load, - graphLoader, - query, - map[image.Reference]string{tt.addImage.ref: tt.addImage.dir}, - nil) - err = add.Populate(registry.ReplacesMode) - if tt.wantErr { - require.True(t, checkAggErr(err, tt.err)) - return - } - require.NoError(t, err) - - for _, p := range tt.wantPackages { - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - result, err := graphLoader.Generate(p.Name) - require.NoError(t, err) - require.Equal(t, p, result) - } - CheckInvariants(t, db) - }) - } -} - -func checkAggErr(aggErr, wantErr error) bool { - var a utilerrors.Aggregate - if errors.As(aggErr, &a) { - for _, e := range a.Errors() { - if reflect.TypeOf(e).String() == reflect.TypeOf(wantErr).String() { - return true - } - } - return false - } - return reflect.TypeOf(aggErr).String() == reflect.TypeOf(wantErr).String() -} - -func TestQuerierForDependencies(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - store, err := createAndPopulateDB(db) - require.NoError(t, err) - - expectedDependencies := []*api.Dependency{ - { - Type: "olm.package", - Value: `{"packageName":"testoperator","version":"\u003e 0.2.0"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"testapi.coreos.com","kind":"testapi","version":"v1"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"testprometheus.coreos.com","kind":"testtestprometheus","version":"v1"}`, - }, - { - Type: "olm.constraint", - Value: `{"cel":{"rule":"properties.exists(p, p.type == \"certified\")"},"failureMessage":"require to have \"certified\""}`, - }, - } - - type operatorbundle struct { - name string - version string - path string - } - - bundlesList := []operatorbundle{ - { - name: "etcdoperator.v0.9.2", - version: "0.9.2", - path: "quay.io/test/etcd.0.9.2", - }, - { - name: "prometheusoperator.0.22.2", - version: "0.22.2", - path: "quay.io/test/prometheus.0.22.2", - }, - } - - dependencies := make([]*api.Dependency, 0, len(bundlesList)) - for _, b := range bundlesList { - dep, err := store.GetDependenciesForBundle(context.TODO(), b.name, b.version, b.path) - require.NoError(t, err) - dependencies = append(dependencies, dep...) - } - require.ElementsMatch(t, expectedDependencies, dependencies) -} - -func TestListBundles(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - store, err := createAndPopulateDB(db) - require.NoError(t, err) - - expectedDependencies := []*api.Dependency{ - { - Type: "olm.package", - Value: `{"packageName":"testoperator","version":"\u003e 0.2.0"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"testapi.coreos.com","kind":"testapi","version":"v1"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"testprometheus.coreos.com","kind":"testtestprometheus","version":"v1"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"testapi.coreos.com","kind":"testapi","version":"v1"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.constraint", - Value: `{"cel":{"rule":"properties.exists(p, p.type == \"certified\")"},"failureMessage":"require to have \"certified\""}`, - }, - { - Type: "olm.constraint", - Value: `{"cel":{"rule":"properties.exists(p, p.type == \"certified\")"},"failureMessage":"require to have \"certified\""}`, - }, - } - - dependencies := []*api.Dependency{} - bundles, err := store.ListBundles(context.TODO()) - require.NoError(t, err) - for _, b := range bundles { - for _, d := range b.Dependencies { - if d.GetType() != "" { - dependencies = append(dependencies, d) - } - } - } - require.Len(t, bundles, 10) - require.ElementsMatch(t, expectedDependencies, dependencies) -} - -func EqualBundles(t *testing.T, expected, actual api.Bundle) { - require.ElementsMatch(t, expected.ProvidedApis, actual.ProvidedApis, "provided apis don't match: %#v\n%#v", expected.ProvidedApis, actual.ProvidedApis) - require.ElementsMatch(t, expected.RequiredApis, actual.RequiredApis, "required apis don't match: %#v\n%#v", expected.RequiredApis, actual.RequiredApis) - require.ElementsMatch(t, expected.Dependencies, actual.Dependencies, "dependencies don't match: %#v\n%#v", expected.Dependencies, actual.Dependencies) - require.ElementsMatch(t, expected.Properties, actual.Properties, "properties don't match %#v\n%#v", expected.Properties, actual.Properties) - expected.RequiredApis, expected.ProvidedApis, actual.RequiredApis, actual.ProvidedApis = nil, nil, nil, nil - expected.Dependencies, expected.Properties, actual.Dependencies, actual.Properties = nil, nil, nil, nil - require.Equal(t, expected, actual) -} - -func CheckInvariants(t *testing.T, db *sql.DB) { - CheckChannelHeadsHaveDescriptions(t, db) - CheckBundlesHaveContentsIfNoPath(t, db) -} - -func CheckChannelHeadsHaveDescriptions(t *testing.T, db *sql.DB) { - // check channel heads have csv / bundle - rows, err := db.Query(` - select operatorbundle.name,length(operatorbundle.csv),length(operatorbundle.bundle) from operatorbundle - join channel on channel.head_operatorbundle_name = operatorbundle.name`) - require.NoError(t, err) - - for rows.Next() { - var name sql.NullString - var csvlen sql.NullInt64 - var bundlelen sql.NullInt64 - err := rows.Scan(&name, &csvlen, &bundlelen) - require.NoError(t, err) - t.Logf("channel head %s has csvlen %d and bundlelen %d", name.String, csvlen.Int64, bundlelen.Int64) - require.NotZero(t, csvlen.Int64, "length of csv for %s should not be zero, it is a channel head", name.String) - require.NotZero(t, bundlelen.Int64, "length of bundle for %s should not be zero, it is a channel head", name.String) - } -} - -func CheckBundlesHaveContentsIfNoPath(t *testing.T, db *sql.DB) { - // check that any bundle entry has csv/bundle content unpacked if there is no bundlepath - rows, err := db.Query(` - select name,length(csv),length(bundle) from operatorbundle - where bundlepath="" or bundlepath=null`) - require.NoError(t, err) - - for rows.Next() { - var name sql.NullString - var csvlen sql.NullInt64 - var bundlelen sql.NullInt64 - err := rows.Scan(&name, &csvlen, &bundlelen) - require.NoError(t, err) - t.Logf("bundle %s has csvlen %d and bundlelen %d", name.String, csvlen.Int64, bundlelen.Int64) - require.NotZero(t, csvlen.Int64, "length of csv for %s should not be zero, it has no bundle path", name.String) - require.NotZero(t, bundlelen.Int64, "length of bundle for %s should not be zero, it has no bundle path", name.String) - } -} - -func TestDirectoryPopulator(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - - loader, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, loader.Migrate(context.TODO())) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - query := sqlite.NewSQLLiteQuerierFromDb(db) - - populate := func(bundles map[image.Reference]string) error { - return registry.NewDirectoryPopulator( - loader, - graphLoader, - query, - bundles, - nil).Populate(registry.ReplacesMode) - } - add := map[image.Reference]string{ - image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", - image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", - } - - err = populate(add) - require.Error(t, err) - require.Contains(t, err.Error(), fmt.Sprintf("Invalid bundle %s, replaces nonexistent bundle %s", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0")) - require.Contains(t, err.Error(), fmt.Sprintf("Invalid bundle %s, replaces nonexistent bundle %s", "prometheusoperator.0.22.2", "prometheusoperator.0.15.0")) -} - -func TestDeprecateBundle(t *testing.T) { - type args struct { - bundles []string - } - type pkgChannel map[string][]string - type expected struct { - err error - remainingBundles []string - deprecatedBundles []string - remainingPkgChannels pkgChannel - } - tests := []struct { - description string - args args - expected expected - }{ - { - description: "IgnoreIfNotInIndex", - args: args{ - bundles: []string{ - "quay.io/test/etcd.0.6.0", - }, - }, - expected: expected{ - err: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - deprecatedBundles: []string{}, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - }, - }, - { - description: "ChannelRemoved", - args: args{ - bundles: []string{ - "quay.io/test/prometheus.0.15.0", - }, - }, - expected: expected{ - err: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - }, - deprecatedBundles: []string{ - "quay.io/test/prometheus.0.15.0/preview", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - }, - }, - }, - }, - { - description: "ChannelRemoved/ErrorOnDefault", - args: args{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - }, - }, - expected: expected{ - err: utilerrors.NewAggregate([]error{fmt.Errorf("error deprecating bundle quay.io/test/prometheus.0.22.2: %s", registry.ErrRemovingDefaultChannelDuringDeprecation)}), - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - deprecatedBundles: []string{}, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - }, - }, - { - description: "OutOfOrderDeprecate", - args: args{ - bundles: []string{ - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - }, - expected: expected{ - err: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - }, - deprecatedBundles: []string{ - "quay.io/test/prometheus.0.15.0/preview", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - querier, err := createAndPopulateDB(db) - require.NoError(t, err) - - store, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - - deprecator := sqlite.NewSQLDeprecatorForBundles(store, tt.args.bundles) - require.Equal(t, tt.expected.err, deprecator.Deprecate()) - - // Ensure remaining bundlePaths in db match - bundles, err := querier.ListBundles(context.Background()) - require.NoError(t, err) - bundlePaths := make([]string, 0, len(bundles)) - for _, bundle := range bundles { - bundlePaths = append(bundlePaths, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - require.ElementsMatch(t, tt.expected.remainingBundles, bundlePaths) - - // Ensure deprecated bundles match - var deprecatedBundles []string - deprecatedProperty, err := json.Marshal(registry.DeprecatedProperty{}) - require.NoError(t, err) - for _, bundle := range bundles { - for _, prop := range bundle.Properties { - if prop.Type == registry.DeprecatedType && prop.Value == string(deprecatedProperty) { - deprecatedBundles = append(deprecatedBundles, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - } - } - - require.ElementsMatch(t, tt.expected.deprecatedBundles, deprecatedBundles) - - // Ensure remaining channels match - packages, err := querier.ListPackages(context.Background()) - require.NoError(t, err) - - for _, pkg := range packages { - channelEntries, err := querier.GetChannelEntriesFromPackage(context.Background(), pkg) - require.NoError(t, err) - - uniqueChannels := make(map[string]struct{}) - channels := make([]string, 0, len(uniqueChannels)) - for _, ch := range channelEntries { - uniqueChannels[ch.ChannelName] = struct{}{} - } - for k := range uniqueChannels { - channels = append(channels, k) - } - require.ElementsMatch(t, tt.expected.remainingPkgChannels[pkg], channels) - } - }) - } -} - -func TestDeprecatePackage(t *testing.T) { - type args struct { - bundles []string - } - type pkgChannel map[string][]string - type expected struct { - err error - remainingBundles []string - deprecatedBundles []string - remainingPkgChannels pkgChannel - } - tests := []struct { - description string - args args - expected expected - }{ - { - description: "RemoveEntirePackage/BundlesAreAllHeadsOfChannels", - args: args{ - bundles: []string{ - "quay.io/test/etcd.0.9.2", - "quay.io/test/etcd.0.9.0", - }, - }, - expected: expected{ - err: nil, - remainingBundles: []string{ - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - deprecatedBundles: []string{}, - remainingPkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - "stable", - }, - }, - }, - }, - { - description: "RemoveHeadOfDefaultChannelWithoutAllChannelHeads/Error", - args: args{ - bundles: []string{ - "quay.io/test/etcd.0.9.2", - }, - }, - expected: expected{ - err: utilerrors.NewAggregate([]error{fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", "etcd", "quay.io/test/etcd.0.9.0", "beta")}), - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.22.2/preview", - }, - deprecatedBundles: []string{}, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - "apicurio-registry": []string{ - "2.x", - "alpha", - }, - }, - }, - }, - { - description: "RemoveHeadOfDefaultChannelWithoutAllChannelHeads/Success", - args: args{ - bundles: []string{ - "quay.io/test/apicurio-registry.v0.0.1", - "quay.io/test/apicurio-registry.v0.0.3-v1.2.3.final", - "quay.io/test/apicurio-registry.v0.0.4-v1.3.2.final", - }, - }, - expected: expected{ - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.22.2/preview", - }, - deprecatedBundles: []string{}, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - "apicurio-registry": []string{ - "2.x", - }, - }, - }, - }, - { - description: "RemoveEntirePackage/AndDeprecateAdditionalBundle", - args: args{ - bundles: []string{ - "quay.io/test/etcd.0.9.2", - "quay.io/test/etcd.0.9.0", - "quay.io/test/prometheus.0.14.0", - }, - }, - expected: expected{ - err: nil, - remainingBundles: []string{ - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - deprecatedBundles: []string{ - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - remainingPkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - "stable", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - querier, err := createAndPopulateDB(db) - require.NoError(t, err) - - store, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - - deprecator := sqlite.NewSQLDeprecatorForBundles(store, tt.args.bundles) - packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, querier) - require.Equal(t, tt.expected.err, packageDeprecator.MaybeRemovePackages()) - if len(tt.expected.deprecatedBundles) > 0 { - require.Equal(t, tt.expected.err, deprecator.Deprecate()) - } - - // Ensure remaining bundlePaths in db match - bundles, err := querier.ListBundles(context.Background()) - require.NoError(t, err) - bundlePaths := make([]string, 0, len(bundles)) - for _, bundle := range bundles { - bundlePaths = append(bundlePaths, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - require.ElementsMatch(t, tt.expected.remainingBundles, bundlePaths) - - // Ensure deprecated bundles match - var deprecatedBundles []string - deprecatedProperty, err := json.Marshal(registry.DeprecatedProperty{}) - require.NoError(t, err) - for _, bundle := range bundles { - for _, prop := range bundle.Properties { - if prop.Type == registry.DeprecatedType && prop.Value == string(deprecatedProperty) { - deprecatedBundles = append(deprecatedBundles, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - } - } - - require.ElementsMatch(t, tt.expected.deprecatedBundles, deprecatedBundles) - - // Ensure remaining channels match - packages, err := querier.ListPackages(context.Background()) - require.NoError(t, err) - - for _, pkg := range packages { - channelEntries, err := querier.GetChannelEntriesFromPackage(context.Background(), pkg) - require.NoError(t, err) - - uniqueChannels := make(map[string]struct{}) - channels := make([]string, 0, len(uniqueChannels)) - for _, ch := range channelEntries { - uniqueChannels[ch.ChannelName] = struct{}{} - } - for k := range uniqueChannels { - channels = append(channels, k) - } - require.ElementsMatch(t, tt.expected.remainingPkgChannels[pkg], channels) - } - }) - } -} - -func TestAddAfterDeprecate(t *testing.T) { - tmpdir := t.TempDir() - - /* - (0.1) 0.1.2 <- 0.1.1 <- 0.1.0 - | - (0.2) 0.2.2 <- 0.2.1 <- 0.2.0<| - | - (0.3) 0.3.2 <- 0.3.1 <- 0.3.0 <| - */ - testBundles := []struct { - suffix string - channels string - defaultChannel string - csvName string - csvSpec json.RawMessage - }{ - { - csvName: "testpkg.v0.1.0", - channels: "0.1", - csvSpec: json.RawMessage(`{"version":"0.1.0","replaces":""}`), - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.1.1", - csvSpec: json.RawMessage(`{"version":"0.1.1","replaces":"testpkg.v0.1.0"}`), - channels: "0.1", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.1.2", - csvSpec: json.RawMessage(`{"version":"0.1.2","replaces":"testpkg.v0.1.1"}`), - channels: "0.1", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.2.0", - csvSpec: json.RawMessage(`{"version":"0.2.0","replaces":"testpkg.v0.1.0"}`), - channels: "0.2", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.2.1", - csvSpec: json.RawMessage(`{"version":"0.2.1","replaces":"testpkg.v0.2.0"}`), - channels: "0.2", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.2.2", - csvSpec: json.RawMessage(`{"version":"0.2.2","replaces":"testpkg.v0.2.1"}`), - channels: "0.2", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.0", - csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":"testpkg.v0.2.1"}`), - channels: "0.3", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.0", - suffix: "overwrite", - csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":""}`), - channels: "0.3", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.0", - csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":"testpkg.v0.2.0"}`), - suffix: "overwrite-replaces-0.2.0", - channels: "0.3", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.1", - csvSpec: json.RawMessage(`{"version":"0.3.1","replaces":"testpkg.v0.3.0"}`), - channels: "0.3", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.1", - suffix: "overwrite", - csvSpec: json.RawMessage(`{"version":"0.3.1","replaces":"testpkg.v0.3.0"}`), - channels: "0.3", - defaultChannel: "0.1", - }, - { - csvName: "testpkg.v0.3.2", - csvSpec: json.RawMessage(`{"version":"0.3.2","replaces":"testpkg.v0.3.1"}`), - channels: "0.3", - defaultChannel: "0.1", - }, - } - for _, b := range testBundles { - dir := b.csvName - if len(b.suffix) > 0 { - dir += "-" + b.suffix - } - _, _, err := newUnpackedTestBundle(tmpdir, dir, b.csvName, b.csvSpec, registry.Annotations{PackageName: "testpkg", Channels: b.channels, DefaultChannelName: b.defaultChannel}) - require.NoError(t, err) - } - - type args struct { - dir string //directory to find the bundles - existing []string - deprecate []string - add []string - overwrite map[string][]string - } - type pkgChannel map[string][]string - type expected struct { - err error - remaining []string - deprecated []string - pkgChannels pkgChannel - } - tests := []struct { - description string - args args - expected expected - }{ - { - description: "SimpleAdd", - args: args{ - dir: "../../bundles/", - existing: []string{ - "prometheus.0.14.0", - "prometheus.0.15.0", - }, - deprecate: []string{ - "quay.io/test/prometheus.0.14.0", - }, - add: []string{ - "prometheus.0.22.2", - }, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/stable", - }, - deprecated: []string{ - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - pkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - "stable", - }, - }, - }, - }, - { - description: "OverwriteLatest", - args: args{ - dir: "../../bundles/", - existing: []string{ - "prometheus.0.14.0", - "prometheus.0.15.0", - "prometheus.0.22.2", - }, - deprecate: []string{ - "quay.io/test/prometheus.0.15.0", - }, - overwrite: map[string][]string{ - "prometheus": { - "prometheus.0.15.0", - "prometheus.0.22.2", - }, - }, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - }, - deprecated: []string{ - "quay.io/test/prometheus.0.15.0/preview", - }, - pkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - }, - }, - }, - }, - { - description: "TruncateTillBranchPoint", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - "testpkg.v0.3.1", - "testpkg.v0.3.2", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.1", - }, - add: []string{ - "testpkg.v0.1.2", - }, - overwrite: nil, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.1.2/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.1/0.3", - "quay.io/test/testpkg.v0.3.2/0.3", - }, - deprecated: []string{ - "quay.io/test/testpkg.v0.3.1/0.3", - }, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - { - description: "DeprecateAboveBranchPoint", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - "testpkg.v0.3.1", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.0", - }, - add: []string{ - "testpkg.v0.1.2", - }, - overwrite: nil, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.1.2/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.0/0.3", - "quay.io/test/testpkg.v0.3.1/0.3", - }, - deprecated: []string{ - "quay.io/test/testpkg.v0.3.0/0.3", - }, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - { - description: "ReplaceDeprecatedChannelHead", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.0", - }, - add: []string{ - "testpkg.v0.3.1", - "testpkg.v0.3.2", - }, - overwrite: nil, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.0/0.3", - "quay.io/test/testpkg.v0.3.1/0.3", - "quay.io/test/testpkg.v0.3.2/0.3", - }, - deprecated: []string{ - "quay.io/test/testpkg.v0.3.0/0.3", - }, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - { - description: "OverwriteDeprecatedChannelHead", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.0", - }, - add: []string{ - "testpkg.v0.3.0-overwrite", - }, - overwrite: map[string][]string{ - "testpkg": {"testpkg.v0.3.0"}, - }, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.0-overwrite/0.3", - }, - deprecated: []string{}, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - { - description: "OverwriteDeprecatedChannelHeadWithReplaces", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.0", - }, - add: []string{ - "testpkg.v0.3.0-overwrite-replaces-0.2.0", - }, - overwrite: map[string][]string{ - "testpkg": {"testpkg.v0.3.0"}, - }, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.0/0.3", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.0/0.3", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.0-overwrite-replaces-0.2.0/0.3", - }, - deprecated: []string{}, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - { - description: "OverwriteAboveDeprecated", - args: args{ - dir: tmpdir, - existing: []string{ - "testpkg.v0.1.0", - "testpkg.v0.1.1", - "testpkg.v0.2.0", - "testpkg.v0.2.1", - "testpkg.v0.2.2", - "testpkg.v0.3.0", - "testpkg.v0.3.1", - }, - deprecate: []string{ - "quay.io/test/testpkg.v0.3.0", - }, - add: []string{ - "testpkg.v0.3.1-overwrite", - }, - overwrite: map[string][]string{ - "testpkg": {"testpkg.v0.3.1"}, - }, - }, - expected: expected{ - err: nil, - remaining: []string{ - "quay.io/test/testpkg.v0.1.0/0.1", - "quay.io/test/testpkg.v0.1.0/0.2", - "quay.io/test/testpkg.v0.1.1/0.1", - "quay.io/test/testpkg.v0.2.0/0.2", - "quay.io/test/testpkg.v0.2.1/0.2", - "quay.io/test/testpkg.v0.2.2/0.2", - "quay.io/test/testpkg.v0.3.0/0.3", - "quay.io/test/testpkg.v0.3.1-overwrite/0.3", - }, - deprecated: []string{ - "quay.io/test/testpkg.v0.3.0/0.3", - }, - pkgChannels: pkgChannel{ - "testpkg": []string{"0.1", "0.2", "0.3"}, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - load, err := sqlite.NewSQLLiteLoader(db, sqlite.WithEnableAlpha(true)) - require.NoError(t, err) - err = load.Migrate(context.TODO()) - require.NoError(t, err) - query := sqlite.NewSQLLiteQuerierFromDb(db) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - populate := func(add []string, overwrite map[string][]string) error { - addRefs := map[image.Reference]string{} - for _, a := range add { - addRefs[image.SimpleReference("quay.io/test/"+a)] = filepath.Join(tt.args.dir, a) - } - - return registry.NewDirectoryPopulator( - load, - graphLoader, - query, - addRefs, - overwrite).Populate(registry.ReplacesMode) - } - // Initialize index with some bundles - require.NoError(t, populate(tt.args.existing, nil)) - - deprecator := sqlite.NewSQLDeprecatorForBundles(load, tt.args.deprecate) - require.Equal(t, tt.expected.err, deprecator.Deprecate()) - - require.NoError(t, populate(tt.args.add, tt.args.overwrite)) - - // Ensure remaining bundlePaths in db match - bundles, err := query.ListBundles(context.Background()) - require.NoError(t, err) - remaining := make([]string, 0, len(bundles)) - for _, bundle := range bundles { - remaining = append(remaining, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - require.ElementsMatch(t, tt.expected.remaining, remaining) - - // Ensure deprecated bundles match - var deprecated []string - deprecatedProperty, err := json.Marshal(registry.DeprecatedProperty{}) - require.NoError(t, err) - for _, bundle := range bundles { - for _, prop := range bundle.Properties { - if prop.Type == registry.DeprecatedType && prop.Value == string(deprecatedProperty) { - deprecated = append(deprecated, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - } - } - - require.ElementsMatch(t, tt.expected.deprecated, deprecated) - - // Ensure remaining channels match - packages, err := query.ListPackages(context.Background()) - require.NoError(t, err) - - for _, pkg := range packages { - channelEntries, err := query.GetChannelEntriesFromPackage(context.Background(), pkg) - require.NoError(t, err) - - uniqueChannels := make(map[string]struct{}) - channels := make([]string, 0, len(uniqueChannels)) - for _, ch := range channelEntries { - uniqueChannels[ch.ChannelName] = struct{}{} - } - for k := range uniqueChannels { - channels = append(channels, k) - } - require.ElementsMatch(t, tt.expected.pkgChannels[pkg], channels) - } - }) - } -} - -func TestOverwrite(t *testing.T) { - type args struct { - firstAdd map[image.Reference]string - secondAdd map[image.Reference]string - overwrites map[string][]string - } - type pkgChannel map[string][]string - type expected struct { - errs []error - remainingBundles []string - remainingPkgChannels pkgChannel - remainingDefaultChannels map[string]string - } - getBundleRefs := func(names []string) map[image.Reference]string { - refs := map[image.Reference]string{} - for _, name := range names { - refs[image.SimpleReference("quay.io/test/"+name)] = "../../bundles/" + name - } - return refs - } - - tests := []struct { - description string - args args - expected expected - }{ - { - description: "DefaultBehavior", - args: args{ - firstAdd: getBundleRefs([]string{"prometheus.0.14.0"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", - image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", - }, - overwrites: nil, - }, - expected: expected{ - errs: []error{ - fmt.Errorf("Invalid bundle %s, replaces nonexistent bundle %s", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"), - fmt.Errorf("Invalid bundle %s, replaces nonexistent bundle %s", "prometheusoperator.0.22.2", "prometheusoperator.0.15.0"), - }, - remainingBundles: []string{ - "quay.io/test/prometheus.0.14.0/preview", - }, - remainingPkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - }, - }, - remainingDefaultChannels: map[string]string{ - "prometheus": "preview", - }, - }, - }, - { - description: "SimpleCsvChange", - args: args{ - firstAdd: getBundleRefs([]string{"etcd.0.9.0", "prometheus.0.14.0", "prometheus.0.15.0"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/new-etcd.0.9.0"): "testdata/overwrite/etcd.0.9.0", - image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", - }, - overwrites: map[string][]string{"etcd": {"etcdoperator.v0.9.0"}}, - }, - expected: expected{ - errs: nil, - remainingBundles: []string{ - "quay.io/test/new-etcd.0.9.0/alpha", - "quay.io/test/new-etcd.0.9.0/beta", - "quay.io/test/new-etcd.0.9.0/stable", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "stable", - "prometheus": "preview", - }, - }, - }, - { - description: "ChannelRemove", - args: args{ - firstAdd: getBundleRefs([]string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", - image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", - }, - overwrites: map[string][]string{"etcd": {"etcdoperator.v0.9.2"}}, - }, - expected: expected{ - errs: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/new-etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.22.2/preview", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "alpha", - "prometheus": "preview", - }, - }, - }, - { - description: "OverwriteBundle/ChannelSwitch", - args: args{ - firstAdd: getBundleRefs([]string{"prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/etcd.0.9.0"): "../../bundles/etcd.0.9.0", - image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", - image.SimpleReference("quay.io/test/new-prometheus.0.22.2"): "testdata/overwrite/prometheus.0.22.2", - }, - overwrites: map[string][]string{"prometheus": {"prometheusoperator.0.22.2"}}, - }, - expected: expected{ - errs: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/new-prometheus.0.22.2/alpha", - "quay.io/test/prometheus.0.15.0/alpha", - "quay.io/test/prometheus.0.14.0/alpha", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - "alpha", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "alpha", - "prometheus": "preview", - }, - }, - }, - { - description: "OverwriteBundle/DefaultChannelChange", - args: args{ - firstAdd: getBundleRefs([]string{"prometheus.0.14.0", "prometheus.0.15.0"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/etcd.0.9.0"): "../../bundles/etcd.0.9.0", - image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", - image.SimpleReference("quay.io/test/new-prometheus.0.15.0"): "testdata/overwrite/prometheus.0.15.0", - }, - overwrites: map[string][]string{"prometheus": {"prometheusoperator.0.15.0"}}, - }, - expected: expected{ - errs: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/new-prometheus.0.15.0/preview", - "quay.io/test/new-prometheus.0.15.0/stable", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "alpha", - "prometheus": "stable", - }, - }, - }, - { - description: "OverwriteBundle/NonLatestOverwrite", - args: args{ - firstAdd: getBundleRefs([]string{"prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/etcd.0.9.0"): "../../bundles/etcd.0.9.0", - image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", - image.SimpleReference("quay.io/test/new-prometheus.0.15.0"): "testdata/overwrite/prometheus.0.15.0", - }, - overwrites: map[string][]string{"prometheus": {"prometheus.0.14.0"}}, - }, - expected: expected{ - errs: []error{registry.OverwriteErr{ErrorString: "Cannot overwrite a bundle that is not at the head of a channel using --overwrite-latest"}}, - remainingBundles: []string{ - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/prometheus.0.22.2/preview", - }, - remainingPkgChannels: pkgChannel{ - "prometheus": []string{ - "preview", - "stable", - }, - }, - remainingDefaultChannels: map[string]string{ - "prometheus": "preview", - }, - }, - }, - { - description: "OverwriteBundle/MultipleOverwrites", - args: args{ - firstAdd: getBundleRefs([]string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", - image.SimpleReference("quay.io/test/new-prometheus.0.22.2"): "testdata/overwrite/prometheus.0.22.2", - }, - overwrites: map[string][]string{ - "prometheus": {"prometheusoperator.0.22.2"}, - "etcd": {"etcdoperator.v0.9.2"}, - }, - }, - expected: expected{ - errs: nil, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/new-etcd.0.9.2/alpha", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - "quay.io/test/new-prometheus.0.22.2/alpha", - "quay.io/test/prometheus.0.15.0/alpha", - "quay.io/test/prometheus.0.14.0/alpha", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "beta", - "alpha", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - "alpha", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "alpha", - "prometheus": "preview", - }, - }, - }, - { - description: "OverwriteBundle/MultipleOverwritesPerPackage", - args: args{ - firstAdd: getBundleRefs([]string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"}), - secondAdd: map[image.Reference]string{ - image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", - image.SimpleReference("quay.io/test/new-new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", - }, - overwrites: map[string][]string{"etcd": {"etcd.0.9.0"}}, - }, - expected: expected{ - errs: []error{registry.OverwriteErr{ErrorString: "Cannot overwrite more than one bundle at a time for a given package using --overwrite-latest"}}, - remainingBundles: []string{ - "quay.io/test/etcd.0.9.0/alpha", - "quay.io/test/etcd.0.9.0/beta", - "quay.io/test/etcd.0.9.0/stable", - "quay.io/test/etcd.0.9.2/alpha", - "quay.io/test/etcd.0.9.2/stable", - "quay.io/test/prometheus.0.22.2/preview", - "quay.io/test/prometheus.0.14.0/preview", - "quay.io/test/prometheus.0.14.0/stable", - "quay.io/test/prometheus.0.15.0/preview", - "quay.io/test/prometheus.0.15.0/stable", - }, - remainingPkgChannels: pkgChannel{ - "etcd": []string{ - "alpha", - "beta", - "stable", - }, - "prometheus": []string{ - "preview", - "stable", - }, - }, - remainingDefaultChannels: map[string]string{ - "etcd": "alpha", - "prometheus": "preview", - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - store, err := sqlite.NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - query := sqlite.NewSQLLiteQuerierFromDb(db) - - populate := func(bundles map[image.Reference]string, overwrites map[string][]string) error { - return registry.NewDirectoryPopulator( - store, - graphLoader, - query, - bundles, - overwrites).Populate(registry.ReplacesMode) - } - require.NoError(t, populate(tt.args.firstAdd, nil)) - - err = populate(tt.args.secondAdd, tt.args.overwrites) - if len(tt.expected.errs) < 1 { - require.NoError(t, err) - } - for _, e := range tt.expected.errs { - require.Contains(t, err.Error(), e.Error()) - } - - // Ensure remaining bundlePaths in db match - bundles, err := query.ListBundles(context.Background()) - require.NoError(t, err) - bundlePaths := make([]string, 0, len(bundles)) - for _, bundle := range bundles { - bundlePaths = append(bundlePaths, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) - } - require.ElementsMatch(t, tt.expected.remainingBundles, bundlePaths) - - // Ensure remaining channels and default channel match - packages, err := query.ListPackages(context.Background()) - require.NoError(t, err) - - for _, pkg := range packages { - channelEntries, err := query.GetChannelEntriesFromPackage(context.Background(), pkg) - require.NoError(t, err) - - uniqueChannels := make(map[string]struct{}) - channels := make([]string, 0, len(uniqueChannels)) - for _, ch := range channelEntries { - uniqueChannels[ch.ChannelName] = struct{}{} - } - for k := range uniqueChannels { - channels = append(channels, k) - } - defaultChannel, err := query.GetDefaultChannelForPackage(context.Background(), pkg) - require.NoError(t, err) - require.ElementsMatch(t, tt.expected.remainingPkgChannels[pkg], channels) - require.Equal(t, tt.expected.remainingDefaultChannels[pkg], defaultChannel) - } - }) - } -} - -func TestSemverPackageManifest(t *testing.T) { - bundle := func(name, version, pkg, defaultChannel, channels string) *registry.Bundle { - b, err := registry.NewBundleFromStrings(name, version, pkg, defaultChannel, channels, "") - require.NoError(t, err) - return b - } - type args struct { - bundles []*registry.Bundle - } - type expect struct { - packageManifest *registry.PackageManifest - hasError bool - } - for _, tt := range []struct { - description string - args args - expect expect - }{ - { - description: "OneUnversioned", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator", "", "package", "stable", "stable"), // version "" is interpreted as 0.0.0-z - }, - }, - expect: expect{ - packageManifest: ®istry.PackageManifest{ - PackageName: "package", - DefaultChannelName: "stable", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "operator", - }, - }, - }, - }, - }, - { - description: "TwoUnversioned", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "", "package", "stable", "stable"), - bundle("operator-2", "", "package", "stable", "stable"), - }, - }, - expect: expect{ - hasError: true, - }, - }, - { - description: "UnversionedAndVersioned", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "", "package", "", "stable"), - bundle("operator-2", "", "package", "", "stable"), - bundle("operator-3", "0.0.1", "package", "", "stable"), // As long as there is one version, we should be good - }, - }, - expect: expect{ - packageManifest: ®istry.PackageManifest{ - PackageName: "package", - DefaultChannelName: "stable", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "operator-3", - }, - }, - }, - }, - }, - { - description: "MaxVersionsAreChannelHeads", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "slow", "slow"), - bundle("operator-2", "1.1.0", "package", "stable", "slow,stable"), - bundle("operator-3", "2.1.0", "package", "stable", "edge"), - }, - }, - expect: expect{ - packageManifest: ®istry.PackageManifest{ - PackageName: "package", - DefaultChannelName: "stable", - Channels: []registry.PackageChannel{ - { - Name: "slow", - CurrentCSVName: "operator-2", - }, - { - Name: "stable", - CurrentCSVName: "operator-2", - }, - { - Name: "edge", - CurrentCSVName: "operator-3", - }, - }, - }, - }, - }, - { - description: "DuplicateVersionsNotTolerated", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "slow", "slow"), - bundle("operator-2", "1.0.0", "package", "stable", "slow,stable"), - bundle("operator-3", "2.1.0", "package", "stable", "edge"), - }, - }, - expect: expect{ - hasError: true, - }, - }, - { - description: "DuplicateVersionsInSeparateChannelsAreTolerated", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "slow", "slow"), - bundle("operator-2", "1.0.0", "package", "stable", "stable"), - bundle("operator-3", "2.1.0", "package", "edge", "edge"), // Should only be tolerated if we have a global max - }, - }, - expect: expect{ - packageManifest: ®istry.PackageManifest{ - PackageName: "package", - DefaultChannelName: "edge", - Channels: []registry.PackageChannel{ - { - Name: "slow", - CurrentCSVName: "operator-1", - }, - { - Name: "stable", - CurrentCSVName: "operator-2", - }, - { - Name: "edge", - CurrentCSVName: "operator-3", - }, - }, - }, - }, - }, - { - description: "DuplicateMaxVersionsAreNotTolerated", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "slow", "slow"), - bundle("operator-2", "1.0.0", "package", "stable", "stable"), - }, - }, - expect: expect{ - hasError: true, - }, - }, - { - description: "UnknownDefaultChannel", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "stable", "stable"), - bundle("operator-2", "2.0.0", "package", "edge", "stable"), - }, - }, - expect: expect{ - hasError: true, - }, - }, - { - description: "BuildIDAndPreReleaseHeads", - args: args{ - bundles: []*registry.Bundle{ - bundle("operator-1", "1.0.0", "package", "stable", "stable"), - bundle("operator-2", "1.0.0+1", "package", "stable", "stable"), - bundle("operator-3", "1.0.0+2", "package", "stable", "stable"), - bundle("operator-4", "2.0.0-pre", "package", "stable", "edge"), - }, - }, - expect: expect{ - packageManifest: ®istry.PackageManifest{ - PackageName: "package", - DefaultChannelName: "stable", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "operator-3", - }, - { - Name: "edge", - CurrentCSVName: "operator-4", - }, - }, - }, - }, - }, - } { - t.Run(tt.description, func(t *testing.T) { - packageManifest, err := registry.SemverPackageManifest(tt.args.bundles) - if tt.expect.hasError { - require.Error(t, err) - return - } - - require.NoError(t, err) - require.NotNil(t, packageManifest) - - expected := tt.expect.packageManifest - require.Equal(t, expected.PackageName, packageManifest.PackageName) - require.Equal(t, expected.DefaultChannelName, packageManifest.DefaultChannelName) - require.ElementsMatch(t, expected.Channels, packageManifest.Channels) - }) - } -} - -func TestSubstitutesFor(t *testing.T) { - type args struct { - bundles []string - rebuilds []string - } - type replaces map[string]string - type expected struct { - bundles []string - substitutions map[string]string - whatReplaces map[string]replaces - defaultChannel string - } - tests := []struct { - description string - args args - expected expected - }{ - { - description: "FirstSubstitutionReplaces", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - }, - rebuilds: []string{ - "prometheus.0.15.0.substitutesfor", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "FirstSubstitutionReplacesNonHead", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - }, - rebuilds: []string{ - "prometheus.0.14.0.substitutesfor", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+0.1234-rebuild", - "stable": "prometheusoperator.0.14.0+0.1234-rebuild", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.15.0", - "stable": "prometheusoperator.0.15.0", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "SecondSubstitutionReplaces", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - }, - rebuilds: []string{ - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "SecondSubstitutionReplacesWithNonHead", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - }, - rebuilds: []string{ - "prometheus.0.14.0.substitutesfor", - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+0.1234-rebuild", - "stable": "prometheusoperator.0.14.0+0.1234-rebuild", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "CanBatchAddSubstitutesFor", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - "prometheus.0.15.0", - }, - rebuilds: []string{}, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "CanBatchAddSubstitutesForWithNonHead", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - "prometheus.0.14.0.substitutesfor", - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - "prometheus.0.15.0", - }, - rebuilds: []string{}, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+0.1234-rebuild", - "stable": "prometheusoperator.0.14.0+0.1234-rebuild", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "CanAddABundleThatReplacesSubstitutedOne", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - "prometheus.0.15.0", - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - }, - rebuilds: []string{ - "prometheus.0.22.2", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "CanAddABundleThatReplacesSubstitutedOneWithNonHead", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - "prometheus.0.15.0", - "prometheus.0.15.0.substitutesfor", - "prometheus.0.15.0.substitutesfor2", - "prometheus.0.14.0.substitutesfor", - }, - rebuilds: []string{ - "prometheus.0.22.2", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.22.2", - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.15.0.substitutesfor2", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.14.0.substitutesfor", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.15.0", - "quay.io/test/prometheus.0.14.0", - }, - substitutions: map[string]string{ - "prometheusoperator.0.22.2": "", - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.15.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - "prometheusoperator.0.15.0+1-freshmaker-rebuild": "prometheusoperator.0.15.0", - "prometheusoperator.0.15.0+2-freshmaker-rebuild": "prometheusoperator.0.15.0+1-freshmaker-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.22.2": { - "preview": "", - }, - "prometheusoperator.0.15.0": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.15.0+2-freshmaker-rebuild": { - "preview": "prometheusoperator.0.22.2", - "stable": "", - }, - "prometheusoperator.0.15.0+1-freshmaker-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+0.1234-rebuild", - "stable": "prometheusoperator.0.14.0+0.1234-rebuild", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - "stable": "prometheusoperator.0.15.0+2-freshmaker-rebuild", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "MultipleBundlesSubstitutingForTheSameBundleAddedInWrongOrder", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - }, - rebuilds: []string{ - "prometheus.0.14.0.substitutesfor", - "prometheus.0.14.0.substitutesfor3", - "prometheus.0.14.0.substitutesfor2", - }, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.14.0.substitutesfor2", - "quay.io/test/prometheus.0.14.0.substitutesfor3", - "quay.io/test/prometheus.0.14.0.substitutesfor", - }, - substitutions: map[string]string{ - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - "prometheusoperator.0.14.0+2-rebuild": "prometheusoperator.0.14.0+0.1234-rebuild", - "prometheusoperator.0.14.0+3-rebuild": "prometheusoperator.0.14.0+2-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+2-rebuild": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+3-rebuild": { - "preview": "", - }, - }, - defaultChannel: "preview", - }, - }, - { - description: "BatchAddWithMultipleBundlesSubstitutingForTheSameBundle", - args: args{ - bundles: []string{ - "prometheus.0.14.0", - "prometheus.0.14.0.substitutesfor", - "prometheus.0.14.0.substitutesfor3", - "prometheus.0.14.0.substitutesfor2", - }, - rebuilds: []string{}, - }, - expected: expected{ - bundles: []string{ - "quay.io/test/prometheus.0.14.0", - "quay.io/test/prometheus.0.14.0.substitutesfor3", - "quay.io/test/prometheus.0.14.0.substitutesfor2", - "quay.io/test/prometheus.0.14.0.substitutesfor", - }, - substitutions: map[string]string{ - "prometheusoperator.0.14.0": "", - "prometheusoperator.0.14.0+0.1234-rebuild": "prometheusoperator.0.14.0", - "prometheusoperator.0.14.0+2-rebuild": "prometheusoperator.0.14.0+0.1234-rebuild", - "prometheusoperator.0.14.0+3-rebuild": "prometheusoperator.0.14.0+2-rebuild", - }, - whatReplaces: map[string]replaces{ - "prometheusoperator.0.14.0": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+0.1234-rebuild": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+2-rebuild": { - "preview": "prometheusoperator.0.14.0+3-rebuild", - }, - "prometheusoperator.0.14.0+3-rebuild": { - "preview": "", - }, - }, - defaultChannel: "preview", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - load, err := sqlite.NewSQLLiteLoader(db, sqlite.WithEnableAlpha(true)) - require.NoError(t, err) - err = load.Migrate(context.TODO()) - require.NoError(t, err) - query := sqlite.NewSQLLiteQuerierFromDb(db) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - populate := func(names []string) error { - refMap := make(map[image.Reference]string, 0) - for _, name := range names { - refMap[image.SimpleReference("quay.io/test/"+name)] = "../../bundles/" + name - } - return registry.NewDirectoryPopulator( - load, - graphLoader, - query, - refMap, - nil).Populate(registry.ReplacesMode) - } - // Initialize index with some bundles - require.NoError(t, populate(tt.args.bundles)) - - // Add the rebuilds one at a time - for _, rb := range tt.args.rebuilds { - require.NoError(t, populate([]string{rb})) - } - - // Check graph is unchanged but has new csv name + version - // Ensure bundlePaths in db match - bundles, err := query.ListBundles(context.Background()) - require.NoError(t, err) - bundlePaths := make([]string, 0, len(bundles)) - for _, bundle := range bundles { - bundlePaths = append(bundlePaths, bundle.BundlePath) - bundleThatReplaces, _ := query.GetBundleThatReplaces(context.Background(), bundle.CsvName, bundle.PackageName, bundle.ChannelName) - if bundleThatReplaces != nil { - require.Equal(t, tt.expected.whatReplaces[bundle.CsvName][bundle.ChannelName], bundleThatReplaces.CsvName) - } else { - require.Empty(t, tt.expected.whatReplaces[bundle.CsvName][bundle.ChannelName]) - } - substitution, err := getBundleSubstitution(context.Background(), db, bundle.CsvName) - require.NoError(t, err) - require.Equal(t, tt.expected.substitutions[bundle.CsvName], substitution) - } - require.ElementsMatch(t, tt.expected.bundles, bundlePaths) - - // check default channel - defaultChannel, err := query.GetDefaultChannelForPackage(context.Background(), "prometheus") - require.NoError(t, err) - require.Equal(t, tt.expected.defaultChannel, defaultChannel) - }) - } -} - -func getBundleSubstitution(ctx context.Context, db *sql.DB, name string) (string, error) { - query := `SELECT substitutesfor FROM operatorbundle WHERE name=?` - rows, err := db.QueryContext(ctx, query, name) - if err != nil { - return "", err - } - defer rows.Close() - - var substitutesFor sql.NullString - if rows.Next() { - if err := rows.Scan(&substitutesFor); err != nil { - return "", err - } - } - return substitutesFor.String, nil -} - -func TestEnableAlpha(t *testing.T) { - type args struct { - bundles []string - enableAlpha bool - } - type expected struct { - err error - } - tests := []struct { - description string - args args - expected expected - }{ - { - description: "SubstitutesForTrue", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - "prometheus.0.15.0.substitutesfor", - }, - enableAlpha: true, - }, - expected: expected{ - err: nil, - }, - }, - { - description: "SubstitutesForFalse", - args: args{ - bundles: []string{ - "prometheus.0.22.2", - "prometheus.0.14.0", - "prometheus.0.15.0", - "prometheus.0.15.0.substitutesfor", - }, - enableAlpha: false, - }, - expected: expected{ - err: utilerrors.NewAggregate([]error{fmt.Errorf("SubstitutesFor is an alpha-only feature. You must enable alpha features with the flag --enable-alpha in order to use this feature.")}), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - load, err := sqlite.NewSQLLiteLoader(db, sqlite.WithEnableAlpha(tt.args.enableAlpha)) - require.NoError(t, err) - err = load.Migrate(context.TODO()) - require.NoError(t, err) - query := sqlite.NewSQLLiteQuerierFromDb(db) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - populate := func(names []string) error { - refMap := make(map[image.Reference]string, 0) - for _, name := range names { - refMap[image.SimpleReference("quay.io/test/"+name)] = "../../bundles/" + name - } - return registry.NewDirectoryPopulator( - load, - graphLoader, - query, - refMap, - nil).Populate(registry.ReplacesMode) - } - require.Equal(t, tt.expected.err, populate(tt.args.bundles)) - }) - } -} - -func newUnpackedTestBundle(root, dir, name string, csvSpec json.RawMessage, annotations registry.Annotations) (string, func(), error) { - bundleDir := filepath.Join(root, dir) - cleanup := func() { - os.RemoveAll(bundleDir) - } - if err := os.Mkdir(bundleDir, 0755); err != nil { - return bundleDir, cleanup, err - } - if err := os.Mkdir(filepath.Join(bundleDir, bundle.ManifestsDir), 0755); err != nil { - return bundleDir, cleanup, err - } - if err := os.Mkdir(filepath.Join(bundleDir, bundle.MetadataDir), 0755); err != nil { - return bundleDir, cleanup, err - } - if len(csvSpec) == 0 { - csvSpec = json.RawMessage(`{}`) - } - - rawCSV, err := json.Marshal(registry.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: sqlite.ClusterServiceVersionKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: csvSpec, - }) - if err != nil { - return bundleDir, cleanup, err - } - - rawObj := unstructured.Unstructured{} - if err := json.Unmarshal(rawCSV, &rawObj); err != nil { - return bundleDir, cleanup, err - } - rawObj.SetCreationTimestamp(metav1.Time{}) - - jsonout, err := rawObj.MarshalJSON() - if err != nil { - return bundleDir, cleanup, err - } - out, err := yaml.JSONToYAML(jsonout) - if err != nil { - return bundleDir, cleanup, err - } - if err := os.WriteFile(filepath.Join(bundleDir, bundle.ManifestsDir, "csv.yaml"), out, 0600); err != nil { - return bundleDir, cleanup, err - } - - out, err = yaml.Marshal(registry.AnnotationsFile{Annotations: annotations}) - if err != nil { - return bundleDir, cleanup, err - } - if err := os.WriteFile(filepath.Join(bundleDir, bundle.MetadataDir, "annotations.yaml"), out, 0600); err != nil { - return bundleDir, cleanup, err - } - return bundleDir, cleanup, nil -} - -func TestValidateEdgeBundlePackage(t *testing.T) { - newInput := func(name, versionString, pkg, defaultChannel, channels, replaces string, skips []string) *registry.ImageInput { - v, err := semver.Parse(versionString) - require.NoError(t, err) - - spec := v1alpha1.ClusterServiceVersionSpec{ - Replaces: replaces, - Skips: skips, - Version: version.OperatorVersion{Version: v}, - } - specJSON, err := json.Marshal(&spec) - require.NoError(t, err) - - rawCSV, err := json.Marshal(registry.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - Kind: sqlite.ClusterServiceVersionKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: specJSON, - }) - require.NoError(t, err) - - rawObj := unstructured.Unstructured{} - require.NoError(t, json.Unmarshal(rawCSV, &rawObj)) - rawObj.SetCreationTimestamp(metav1.Time{}) - - jsonout, err := rawObj.MarshalJSON() - require.NoError(t, err) - - b, err := registry.NewBundleFromStrings(name, versionString, pkg, defaultChannel, channels, string(jsonout)) - require.NoError(t, err) - return ®istry.ImageInput{Bundle: b} - } - - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - - store, err := createAndPopulateDB(db) - require.NoError(t, err) - - r := registry.NewDirectoryPopulator(nil, nil, store, nil, nil) - - tests := []struct { - name string - args []*registry.ImageInput - wantErr error - }{ - { - name: "conflictInAdded", - args: []*registry.ImageInput{ - newInput("b1", "0.0.1", "p1", "a", "a", "", []string{}), - newInput("b2", "0.0.2", "p2", "a", "a", "", []string{"b1"}), - }, - wantErr: fmt.Errorf("bundle b1 must belong to exactly one package, found on: [p1 p2]"), - }, - { - name: "conflictWithExisting", - args: []*registry.ImageInput{ - newInput("b1", "0.0.1", "p1", "a", "a", "", []string{"etcdoperator.v0.9.0"}), - }, - wantErr: fmt.Errorf("bundle etcdoperator.v0.9.0 belongs to package etcd on index, cannot be added as an edge for package p1"), - }, - { - name: "passForNonConflicting", - args: []*registry.ImageInput{ - newInput("b1", "0.0.1", "etcd", "a", "a", "", []string{"etcdoperator.v0.9.0"}), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := r.ValidateEdgeBundlePackage(tt.args) - if tt.wantErr == nil { - require.NoError(t, err) - return - } - require.EqualError(t, err, tt.wantErr.Error()) - }) - } -} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index e74b941f9..5bb2d8563 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -21,19 +21,12 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/connectivity" - "github.com/operator-framework/operator-registry/alpha/action" - "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/pkg/api" fbccache "github.com/operator-framework/operator-registry/pkg/cache" "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" ) const ( - dbPort = ":50052" - dbAddress = "localhost" + dbPort - dbName = "test.db" - cachePort = ":50053" cacheAddress = "localhost" + cachePort @@ -41,36 +34,6 @@ const ( deprecationCacheAddress = "localhost" + deprecationCachePort ) -func createDBStore(dbPath string) *sqlite.SQLQuerier { - db, err := sqlite.Open(dbPath) - if err != nil { - logrus.Fatal(err) - } - load, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - logrus.Fatal(err) - } - if err := load.Migrate(context.TODO()); err != nil { - logrus.Fatal(err) - } - - loader := sqlite.NewSQLLoaderForDirectory(load, "../../manifests") - 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, sqlite.OmitManifests(true)) - if err != nil { - logrus.Fatal(err) - } - return store -} - func fbcCache(catalogDir, cacheDir string) (fbccache.Cache, error) { store, err := fbccache.New(cacheDir) if err != nil { @@ -116,28 +79,6 @@ func TestMain(m *testing.M) { } }() - dbFile := filepath.Join(tmpDir, "test.db") - dbStore := createDBStore(dbFile) - - fbcDir := filepath.Join(tmpDir, "fbc") - fbcMigrate := action.Migrate{ - CatalogRef: dbFile, - OutputDir: fbcDir, - WriteFunc: declcfg.WriteJSON, - FileExt: ".json", - } - if err := fbcMigrate.Run(context.TODO()); err != nil { - logrus.Fatal(err) - } - - grpcServer := server(dbStore) - - fbcStore, err := fbcCache(fbcDir, filepath.Join(tmpDir, "cache")) - if err != nil { - logrus.Fatalf("failed to create cache: %v", err) - } - fbcServerSimple := server(fbcStore) - fbcDeprecationStore, err := fbcCacheFromFs(validFS, filepath.Join(tmpDir, "deprecation-cache")) if err != nil { logrus.Fatalf("failed to create deprecation cache: %v", err) @@ -145,27 +86,7 @@ func TestMain(m *testing.M) { fbcServerDeprecations := server(fbcDeprecationStore) var wg sync.WaitGroup - wg.Add(3) - go func() { - lis, err := net.Listen("tcp", fmt.Sprintf("localhost%s", dbPort)) - if err != nil { - logrus.Fatalf("failed to listen: %v", err) - } - wg.Done() - if err := grpcServer.Serve(lis); err != nil { - logrus.Fatalf("failed to serve db: %v", err) - } - }() - go func() { - lis, err := net.Listen("tcp", fmt.Sprintf("localhost%s", cachePort)) - if err != nil { - logrus.Fatalf("failed to listen: %v", err) - } - wg.Done() - if err := fbcServerSimple.Serve(lis); err != nil { - logrus.Fatalf("failed to serve fbc cache: %v", err) - } - }() + wg.Add(1) go func() { lis, err := net.Listen("tcp", deprecationCacheAddress) if err != nil { @@ -196,12 +117,9 @@ func client(t *testing.T, address string) (api.RegistryClient, *grpc.ClientConn) func TestListPackages(t *testing.T) { var ( - listPackagesExpected = []string{"etcd", "prometheus", "strimzi-kafka-operator"} listPackagesExpectedDep = []string{"cockroachdb"} ) - t.Run("Sqlite", testListPackages(dbAddress, listPackagesExpected)) - t.Run("FBCCache", testListPackages(cacheAddress, listPackagesExpected)) t.Run("FBCCacheWithDeprecations", testListPackages(deprecationCacheAddress, listPackagesExpectedDep)) } @@ -234,25 +152,6 @@ func testListPackages(addr string, expected []string) func(*testing.T) { func TestGetPackage(t *testing.T) { var ( - getPackageExpected = &api.Package{ - Name: "etcd", - Channels: []*api.Channel{ - { - Name: "alpha", - CsvName: "etcdoperator.v0.9.2", - }, - { - Name: "beta", - CsvName: "etcdoperator.v0.9.0", - }, - { - Name: "stable", - CsvName: "etcdoperator.v0.9.2", - }, - }, - DefaultChannelName: "alpha", - } - getPackageExpectedDep = &api.Package{ Name: "cockroachdb", Channels: []*api.Channel{ @@ -276,8 +175,6 @@ func TestGetPackage(t *testing.T) { }, } ) - t.Run("Sqlite", testGetPackage(dbAddress, getPackageExpected)) - t.Run("FBCCache", testGetPackage(cacheAddress, getPackageExpected)) t.Run("FBCCacheWithDeprecations", testGetPackage(deprecationCacheAddress, getPackageExpectedDep)) } @@ -328,8 +225,6 @@ func TestGetBundle(t *testing.T) { }, } ) - t.Run("Sqlite", testGetBundle(dbAddress, etcdoperatorV0_9_2("alpha", false, false, includeManifestsAll))) - t.Run("FBCCache", testGetBundle(cacheAddress, etcdoperatorV0_9_2("alpha", false, true, includeManifestsAll))) t.Run("FBCCacheWithDeprecations", testGetBundle(deprecationCacheAddress, cockroachBundle)) } @@ -346,14 +241,6 @@ func testGetBundle(addr string, expected *api.Bundle) func(*testing.T) { } func TestGetBundleForChannel(t *testing.T) { - { - b := etcdoperatorV0_9_2("alpha", false, false, includeManifestsAll) - t.Run("Sqlite", testGetBundleForChannel(dbAddress, &api.Bundle{ - CsvName: b.CsvName, - CsvJson: b.CsvJson + "\n", - })) - } - t.Run("FBCCache", testGetBundleForChannel(cacheAddress, etcdoperatorV0_9_2("alpha", false, true, includeManifestsAll))) } func testGetBundleForChannel(addr string, expected *api.Bundle) func(*testing.T) { @@ -370,27 +257,6 @@ func testGetBundleForChannel(addr string, expected *api.Bundle) func(*testing.T) func TestGetChannelEntriesThatReplace(t *testing.T) { var ( - getChannelEntriesThatReplaceExpected = []*api.ChannelEntry{ - { - PackageName: "etcd", - ChannelName: "alpha", - BundleName: "etcdoperator.v0.9.0", - Replaces: "etcdoperator.v0.6.1", - }, - { - PackageName: "etcd", - ChannelName: "beta", - BundleName: "etcdoperator.v0.9.0", - Replaces: "etcdoperator.v0.6.1", - }, - { - PackageName: "etcd", - ChannelName: "stable", - BundleName: "etcdoperator.v0.9.0", - Replaces: "etcdoperator.v0.6.1", - }, - } - getChannelEntriesThatReplaceExpectedDep = []*api.ChannelEntry{ { PackageName: "cockroachdb", @@ -401,8 +267,6 @@ func TestGetChannelEntriesThatReplace(t *testing.T) { } ) - t.Run("Sqlite", testGetChannelEntriesThatReplace(dbAddress, getChannelEntriesThatReplaceExpected)) - t.Run("FBCCache", testGetChannelEntriesThatReplace(cacheAddress, getChannelEntriesThatReplaceExpected)) t.Run("FBCCacheWithDeprecations", testGetChannelEntriesThatReplace(deprecationCacheAddress, getChannelEntriesThatReplaceExpectedDep)) } @@ -458,8 +322,6 @@ func testGetChannelEntriesThatReplace(addr string, expected []*api.ChannelEntry) } func TestGetBundleThatReplaces(t *testing.T) { - t.Run("Sqlite", testGetBundleThatReplaces(dbAddress, etcdoperatorV0_9_2("alpha", false, false, includeManifestsAll))) - t.Run("FBCCache", testGetBundleThatReplaces(cacheAddress, etcdoperatorV0_9_2("alpha", false, true, includeManifestsAll))) } func testGetBundleThatReplaces(addr string, expected *api.Bundle) func(*testing.T) { @@ -474,8 +336,6 @@ func testGetBundleThatReplaces(addr string, expected *api.Bundle) func(*testing. } func TestGetBundleThatReplacesSynthetic(t *testing.T) { - t.Run("Sqlite", testGetBundleThatReplacesSynthetic(dbAddress, etcdoperatorV0_9_2("alpha", false, false, includeManifestsAll))) - t.Run("FBCCache", testGetBundleThatReplacesSynthetic(cacheAddress, etcdoperatorV0_9_2("alpha", false, true, includeManifestsAll))) } func testGetBundleThatReplacesSynthetic(addr string, expected *api.Bundle) func(*testing.T) { @@ -491,8 +351,6 @@ func testGetBundleThatReplacesSynthetic(addr string, expected *api.Bundle) func( } func TestGetChannelEntriesThatProvide(t *testing.T) { - t.Run("Sqlite", testGetChannelEntriesThatProvide(dbAddress)) - t.Run("FBCCache", testGetChannelEntriesThatProvide(cacheAddress)) } func testGetChannelEntriesThatProvide(addr string) func(t *testing.T) { @@ -608,8 +466,6 @@ func testGetChannelEntriesThatProvide(addr string) func(t *testing.T) { } func TestGetLatestChannelEntriesThatProvide(t *testing.T) { - t.Run("Sqlite", testGetLatestChannelEntriesThatProvide(dbAddress)) - t.Run("FBCCache", testGetLatestChannelEntriesThatProvide(cacheAddress)) } func testGetLatestChannelEntriesThatProvide(addr string) func(t *testing.T) { @@ -684,8 +540,6 @@ func testGetLatestChannelEntriesThatProvide(addr string) func(t *testing.T) { } func TestGetDefaultBundleThatProvides(t *testing.T) { - t.Run("Sqlite", testGetDefaultBundleThatProvides(dbAddress, etcdoperatorV0_9_2("alpha", false, false, includeManifestsAll))) - t.Run("FBCCache", testGetDefaultBundleThatProvides(cacheAddress, etcdoperatorV0_9_2("alpha", false, true, includeManifestsAll))) } func testGetDefaultBundleThatProvides(addr string, expected *api.Bundle) func(*testing.T) { @@ -700,12 +554,6 @@ func testGetDefaultBundleThatProvides(addr string, expected *api.Bundle) func(*t } func TestListBundles(t *testing.T) { - t.Run("Sqlite", testListBundles(dbAddress, - etcdoperatorV0_9_2("alpha", true, false, includeManifestsNone), - etcdoperatorV0_9_2("stable", true, false, includeManifestsNone))) - t.Run("FBCCache", testListBundles(cacheAddress, - etcdoperatorV0_9_2("alpha", true, true, includeManifestsNone), - etcdoperatorV0_9_2("stable", true, true, includeManifestsNone))) } func testListBundles(addr string, etcdAlpha *api.Bundle, etcdStable *api.Bundle) func(*testing.T) { diff --git a/pkg/sqlite/configmap.go b/pkg/sqlite/configmap.go deleted file mode 100644 index a1ce927f8..000000000 --- a/pkg/sqlite/configmap.go +++ /dev/null @@ -1,187 +0,0 @@ -package sqlite - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const ( - ConfigMapCRDName = "customResourceDefinitions" - ConfigMapCSVName = "clusterServiceVersions" - ConfigMapPackageName = "packages" -) - -// ConfigMapLoader loads a configmap of resources into the database -// entries under "customResourceDefinitions" will be parsed as CRDs -// entries under "clusterServiceVersions" will be parsed as CSVs -// entries under "packages" will be parsed as Packages -type ConfigMapLoader struct { - log *logrus.Entry - store registry.Load - configMapData map[string]string - crds map[registry.APIKey]*unstructured.Unstructured -} - -var _ SQLPopulator = &ConfigMapLoader{} - -// NewSQLLoaderForConfigMapData is useful when the operator manifest(s) -// originate from a different source than a configMap. For example, operator -// manifest(s) can be downloaded from a remote registry like quay.io. -func NewSQLLoaderForConfigMapData(logger *logrus.Entry, store registry.Load, configMapData map[string]string) *ConfigMapLoader { - return &ConfigMapLoader{ - log: logger, - store: store, - configMapData: configMapData, - crds: map[registry.APIKey]*unstructured.Unstructured{}, - } -} - -func NewSQLLoaderForConfigMap(store registry.Load, configMap corev1.ConfigMap) *ConfigMapLoader { - logger := logrus.WithFields(logrus.Fields{"configmap": configMap.GetName(), "ns": configMap.GetNamespace()}) - return &ConfigMapLoader{ - log: logger, - store: store, - configMapData: configMap.Data, - crds: map[registry.APIKey]*unstructured.Unstructured{}, - } -} - -func (c *ConfigMapLoader) Populate() error { - c.log.Info("loading CRDs") - - // first load CRDs into memory; these will be added to the bundle that owns them - crdListYaml, ok := c.configMapData[ConfigMapCRDName] - if !ok { - return fmt.Errorf("couldn't find expected key %s in configmap", ConfigMapCRDName) - } - - crdListJSON, err := yaml.YAMLToJSON([]byte(crdListYaml)) - if err != nil { - c.log.WithError(err).Debug("error loading CRD list") - return err - } - - var parsedCRDList []v1beta1.CustomResourceDefinition - if err := json.Unmarshal(crdListJSON, &parsedCRDList); err != nil { - c.log.WithError(err).Debug("error parsing CRD list") - return err - } - - var errs []error - for _, crd := range parsedCRDList { - if crd.Spec.Versions == nil && crd.Spec.Version != "" { - crd.Spec.Versions = []v1beta1.CustomResourceDefinitionVersion{{Name: crd.Spec.Version, Served: true, Storage: true}} - } - for _, version := range crd.Spec.Versions { - gvk := registry.APIKey{Group: crd.Spec.Group, Version: version.Name, Kind: crd.Spec.Names.Kind, Plural: crd.Spec.Names.Plural} - c.log.WithField("gvk", gvk).Debug("loading CRD") - if _, ok := c.crds[gvk]; ok { - c.log.WithField("gvk", gvk).Debug("crd added twice") - errs = append(errs, fmt.Errorf("can't add the same CRD twice in one configmap: %v", gvk)) - continue - } - crdUnst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&crd) - if err != nil { - errs = append(errs, fmt.Errorf("error marshaling crd: %s", err)) - continue - } - c.crds[gvk] = &unstructured.Unstructured{Object: crdUnst} - } - } - - c.log.Info("loading Bundles") - csvListYaml, ok := c.configMapData[ConfigMapCSVName] - if !ok { - errs = append(errs, fmt.Errorf("couldn't find expected key %s in configmap", ConfigMapCSVName)) - return utilerrors.NewAggregate(errs) - } - csvListJSON, err := yaml.YAMLToJSON([]byte(csvListYaml)) - if err != nil { - errs = append(errs, fmt.Errorf("error loading CSV list: %s", err)) - return utilerrors.NewAggregate(errs) - } - - var parsedCSVList []registry.ClusterServiceVersion - err = json.Unmarshal(csvListJSON, &parsedCSVList) - if err != nil { - errs = append(errs, fmt.Errorf("error parsing CSV list: %s", err)) - return utilerrors.NewAggregate(errs) - } - - for _, csv := range parsedCSVList { - c.log.WithField("csv", csv.GetName()).Debug("loading CSV") - csvUnst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&csv) - if err != nil { - errs = append(errs, fmt.Errorf("error marshaling csv: %s", err)) - continue - } - - bundle := registry.NewBundle(csv.GetName(), ®istry.Annotations{}, &unstructured.Unstructured{Object: csvUnst}) - ownedCRDs, _, err := csv.GetCustomResourceDefintions() - if err != nil { - errs = append(errs, err) - continue - } - for _, owned := range ownedCRDs { - split := strings.SplitN(owned.Name, ".", 2) - if len(split) < 2 { - c.log.WithError(err).Debug("error parsing owned name") - errs = append(errs, fmt.Errorf("error parsing owned name: %s", err)) - continue - } - - gvk := registry.APIKey{Group: split[1], Version: owned.Version, Kind: owned.Kind, Plural: split[0]} - crdUnst, ok := c.crds[gvk] - if !ok { - errs = append(errs, fmt.Errorf("couldn't find owned CRD in crd list %v: %s", gvk, err)) - continue - } - - bundle.Add(crdUnst) - } - - if err := c.store.AddOperatorBundle(bundle); err != nil { - version, _ := bundle.Version() - errs = append(errs, fmt.Errorf("error adding operator bundle %s/%s/%s: %s", csv.GetName(), version, bundle.BundleImage, err)) - } - } - - c.log.Info("loading Packages") - packageListYaml, ok := c.configMapData[ConfigMapPackageName] - if !ok { - errs = append(errs, fmt.Errorf("couldn't find expected key %s in configmap", ConfigMapPackageName)) - return utilerrors.NewAggregate(errs) - } - - packageListJSON, err := yaml.YAMLToJSON([]byte(packageListYaml)) - if err != nil { - errs = append(errs, fmt.Errorf("error loading package list: %s", err)) - return utilerrors.NewAggregate(errs) - } - - var parsedPackageManifests []registry.PackageManifest - err = json.Unmarshal(packageListJSON, &parsedPackageManifests) - if err != nil { - errs = append(errs, fmt.Errorf("error parsing package list: %s", err)) - return utilerrors.NewAggregate(errs) - } - for _, packageManifest := range parsedPackageManifests { - c.log.WithField("package", packageManifest.PackageName).Debug("loading package") - if err := c.store.AddPackageChannels(packageManifest); err != nil { - errs = append(errs, fmt.Errorf("error loading package %s: %s", packageManifest.PackageName, err)) - } - } - - return utilerrors.NewAggregate(errs) -} diff --git a/pkg/sqlite/configmap_test.go b/pkg/sqlite/configmap_test.go deleted file mode 100644 index 75fee43b8..000000000 --- a/pkg/sqlite/configmap_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package sqlite - -import ( - "bytes" - "context" - "crypto/rand" - "database/sql" - "fmt" - "math" - "math/big" - "os" - "strings" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/yaml" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func CreateTestDB(t *testing.T) (*sql.DB, func()) { - r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - require.NoError(t, err) - dbName := fmt.Sprintf("test-%d.db", r.Int64()) - - db, err := Open(dbName) - require.NoError(t, err) - - return db, func() { - defer func() { - if err := os.Remove(dbName); err != nil { - t.Fatal(err) - } - }() - if err := db.Close(); err != nil { - t.Fatal(err) - } - } -} - -func TestConfigMapLoader(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - defer os.Remove("test.db") - require.NoError(t, store.Migrate(context.TODO())) - - path := "../../configmap.example.yaml" - fileReader, err := os.Open(path) - require.NoError(t, err, "unable to load configmap from file %s", path) - - decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - manifest := corev1.ConfigMap{} - err = decoder.Decode(&manifest) - require.NoError(t, err, "could not decode contents of file %s into configmap", path) - - loader := NewSQLLoaderForConfigMap(store, manifest) - require.NoError(t, loader.Populate()) -} - -func TestReplaceCycle(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - - path := "../../configmap.example.yaml" - cmap, err := os.ReadFile(path) - - require.NoError(t, err, "unable to load configmap from file %s", path) - - // Make etcdoperator.v0.9.0 in the example replace 0.9.2 to create a loop - sReader := strings.NewReader(string(bytes.Replace(cmap, - []byte("replaces: etcdoperator.v0.6.1"), - []byte("replaces: etcdoperator.v0.9.2"), 1))) - - decoder := yaml.NewYAMLOrJSONDecoder(sReader, 30) - manifest := corev1.ConfigMap{} - err = decoder.Decode(&manifest) - require.NoError(t, err, "could not decode contents of file %s into configmap", path) - - loader := NewSQLLoaderForConfigMap(store, manifest) - err = loader.Populate() - require.Error(t, err, "Cycle detected, etcdoperator.v0.9.0 replaces etcdoperator.v0.9.2") -} - -func TestQuerierForConfigmap(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - load, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, load.Migrate(context.TODO())) - - path := "../../configmap.example.yaml" - fileReader, err := os.Open(path) - require.NoError(t, err, "unable to load configmap from file %s", path) - - decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - manifest := corev1.ConfigMap{} - err = decoder.Decode(&manifest) - require.NoError(t, err, "could not decode contents of file %s into configmap", path) - - loader := NewSQLLoaderForConfigMap(load, manifest) - require.NoError(t, loader.Populate()) - - store := NewSQLLiteQuerierFromDb(db) - - foundPackages, err := store.ListPackages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{"etcd", "prometheus"}, foundPackages) - - etcdPackage, err := store.GetPackage(context.TODO(), "etcd") - require.NoError(t, err) - require.Equal(t, ®istry.PackageManifest{ - PackageName: "etcd", - DefaultChannelName: "alpha", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "etcdoperator.v0.9.2", - }, - }, - }, etcdPackage) - - etcdBundleByChannel, err := store.GetBundleForChannel(context.TODO(), "etcd", "alpha") - require.NoError(t, err) - expectedBundle := &api.Bundle{ - CsvName: "etcdoperator.v0.9.2", - PackageName: "etcd", - ChannelName: "alpha", - BundlePath: "", - ProvidedApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdBackup", Plural: "etcdbackups"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdRestore", Plural: "etcdrestores"}, - }, - RequiredApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - }, - Dependencies: []*api.Dependency{ - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - }, - Properties: []*api.Property{ - { - Type: "olm.package", - Value: `{"packageName":"etcd","version":"0.9.2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdBackup","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdRestore","version":"v1beta2"}`, - }, - }, - Version: "0.9.2", - SkipRange: "< 0.6.0", - 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.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 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\"}},\"version\":\"0.9.2\"}}", - Object: []string{ - "{\"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.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 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\"}},\"version\":\"0.9.2\"}}", - "{\"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\",\"versions\":[{\"name\":\"v1beta2\",\"served\":true,\"storage\":true}]},\"status\":{\"acceptedNames\":{\"kind\":\"\",\"plural\":\"\"},\"conditions\":null,\"storedVersions\":null}}", - "{\"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\",\"versions\":[{\"name\":\"v1beta2\",\"served\":true,\"storage\":true}]},\"status\":{\"acceptedNames\":{\"kind\":\"\",\"plural\":\"\"},\"conditions\":null,\"storedVersions\":null}}", - "{\"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\",\"versions\":[{\"name\":\"v1beta2\",\"served\":true,\"storage\":true}]},\"status\":{\"acceptedNames\":{\"kind\":\"\",\"plural\":\"\"},\"conditions\":null,\"storedVersions\":null}}"}, - } - bareGetBundleForChannelResult := &api.Bundle{ - CsvName: expectedBundle.CsvName, - CsvJson: expectedBundle.CsvJson + "\n", - } - EqualBundles(t, *bareGetBundleForChannelResult, *etcdBundleByChannel) - - etcdBundle, err := store.GetBundle(context.TODO(), "etcd", "alpha", "etcdoperator.v0.9.2") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundle) - - etcdChannelEntries, err := store.GetChannelEntriesThatReplace(context.TODO(), "etcdoperator.v0.9.0") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.9.2", Replaces: "etcdoperator.v0.9.0"}}, etcdChannelEntries) - - etcdBundleByReplaces, err := store.GetBundleThatReplaces(context.TODO(), "etcdoperator.v0.9.0", "etcd", "alpha") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByReplaces) - - etcdChannelEntriesThatProvide, err := store.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{ - {PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.6.1", Replaces: ""}, - {PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.9.0", Replaces: "etcdoperator.v0.6.1"}, - {PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.9.2", Replaces: "etcdoperator.v0.9.0"}}, etcdChannelEntriesThatProvide) - - etcdChannelEntriesThatProvideAPIServer, err := store.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "FakeEtcdObject") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.9.0", Replaces: "etcdoperator.v0.6.1"}}, etcdChannelEntriesThatProvideAPIServer) - - etcdLatestChannelEntriesThatProvide, err := store.GetLatestChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{PackageName: "etcd", ChannelName: "alpha", BundleName: "etcdoperator.v0.9.2", Replaces: "etcdoperator.v0.9.0"}}, etcdLatestChannelEntriesThatProvide) - - etcdBundleByProvides, err := store.GetBundleThatProvides(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByProvides) - - expectedEtcdImages := []string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - "quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84", - "quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f", - } - etcdImages, err := store.GetImagesForBundle(context.TODO(), "etcdoperator.v0.6.1") - require.NoError(t, err) - require.ElementsMatch(t, expectedEtcdImages, etcdImages) - - expectedDatabaseImages := []string{ - "quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f", - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - "quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84", - "quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8", - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - "quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731", - "quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d", - "quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf", - } - dbImages, err := store.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, expectedDatabaseImages, dbImages) -} diff --git a/pkg/sqlite/conversion.go b/pkg/sqlite/conversion.go deleted file mode 100644 index 47d2257f7..000000000 --- a/pkg/sqlite/conversion.go +++ /dev/null @@ -1,144 +0,0 @@ -package sqlite - -import ( - "context" - "encoding/base64" - "encoding/json" - "fmt" - "strings" - - "github.com/sirupsen/logrus" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - - "github.com/operator-framework/operator-registry/alpha/model" - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func ToModel(ctx context.Context, q *SQLQuerier) (model.Model, error) { - pkgs, err := initializeModelPackages(ctx, q) - if err != nil { - return nil, err - } - if err := populateModelChannels(ctx, pkgs, q); err != nil { - return nil, fmt.Errorf("populate channels: %v", err) - } - if err := populatePackageIcons(ctx, pkgs, q); err != nil { - return nil, fmt.Errorf("populate package icons: %v", err) - } - if err := pkgs.Validate(); err != nil { - return nil, err - } - pkgs.Normalize() - return pkgs, nil -} - -func initializeModelPackages(ctx context.Context, q *SQLQuerier) (model.Model, error) { - pkgNames, err := q.ListPackages(ctx) - if err != nil { - return nil, err - } - - // nolint:prealloc - var rPkgs []registry.PackageManifest - for _, pkgName := range pkgNames { - rPkg, err := q.GetPackage(ctx, pkgName) - if err != nil { - return nil, err - } - rPkgs = append(rPkgs, *rPkg) - } - - pkgs := model.Model{} - for _, rPkg := range rPkgs { - pkg := model.Package{ - Name: rPkg.PackageName, - Channels: map[string]*model.Channel{}, - } - - for _, ch := range rPkg.Channels { - channel := &model.Channel{ - Package: &pkg, - Name: ch.Name, - Bundles: map[string]*model.Bundle{}, - } - if ch.Name == rPkg.DefaultChannelName { - pkg.DefaultChannel = channel - } - pkg.Channels[ch.Name] = channel - } - pkgs[pkg.Name] = &pkg - } - return pkgs, nil -} - -func populateModelChannels(ctx context.Context, pkgs model.Model, q *SQLQuerier) error { - bundles, err := q.ListBundles(ctx) - if err != nil { - return err - } - -ConvertBundles: - for _, bundle := range bundles { - for _, prop := range bundle.Properties { - if prop.Type == registry.DeprecatedType { - // bundle contains `olm.Deprecated` property - // exclude this bundle from being rendered - continue ConvertBundles - } - } - pkg, ok := pkgs[bundle.PackageName] - if !ok { - return fmt.Errorf("unknown package %q for bundle %q", bundle.PackageName, bundle.CsvName) - } - - pkgChannel, ok := pkg.Channels[bundle.ChannelName] - if !ok { - return fmt.Errorf("unknown channel %q for bundle %q", bundle.ChannelName, bundle.CsvName) - } - - mbundle, err := api.ConvertAPIBundleToModelBundle(bundle) - if err != nil { - return fmt.Errorf("convert bundle %q: %v", bundle.CsvName, err) - } - mbundle.Package = pkg - mbundle.Channel = pkgChannel - pkgChannel.Bundles[bundle.CsvName] = mbundle - } - return nil -} - -// populatePackageIcons populates the package icons from the icon of bundle of the head -// of the default channel of each of the pacakges in pkgs. -func populatePackageIcons(ctx context.Context, pkgs model.Model, q *SQLQuerier) error { - for _, pkg := range pkgs { - head, err := q.GetBundleForChannel(ctx, pkg.Name, pkg.DefaultChannel.Name) - if err != nil { - return fmt.Errorf("get default channel head for package %q: %v", pkg.Name, err) - } - var csv v1alpha1.ClusterServiceVersion - if err := json.Unmarshal([]byte(head.CsvJson), &csv); err != nil { - return fmt.Errorf("unmarshal CSV json for bundle %q: %v", head.CsvName, err) - } - if len(csv.Spec.Icon) == 0 { - continue - } - iconData, origErr := base64.StdEncoding.DecodeString(csv.Spec.Icon[0].Data) - if origErr != nil { - // Try decoding after removing spaces (this is a problem with the planetscale operator). - iconData, err = base64.StdEncoding.DecodeString(strings.ReplaceAll(csv.Spec.Icon[0].Data, " ", "")) - if err != nil { - logrus.WithError(err).Warnf("base64 decode CSV icon for bundle %q", head.CsvName) - continue - } - } - if len(iconData) > 0 { - pkg.Icon = &model.Icon{ - Data: iconData, - MediaType: csv.Spec.Icon[0].MediaType, - } - } - } - return nil -} diff --git a/pkg/sqlite/conversion_test.go b/pkg/sqlite/conversion_test.go deleted file mode 100644 index 077b95c6d..000000000 --- a/pkg/sqlite/conversion_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package sqlite - -import ( - "context" - "path/filepath" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" -) - -func TestToModel(t *testing.T) { - tmpDir := t.TempDir() - dbPath := filepath.Join(tmpDir, "test.db") - - db, err := Open(dbPath) - if err != nil { - logrus.Fatal(err) - } - load, err := NewSQLLiteLoader(db) - if err != nil { - logrus.Fatal(err) - } - if err := load.Migrate(context.TODO()); err != nil { - logrus.Fatal(err) - } - - loader := NewSQLLoaderForDirectory(load, "../../manifests") - if err := loader.Populate(); err != nil { - logrus.Fatal(err) - } - if err := db.Close(); err != nil { - logrus.Fatal(err) - } - store, err := NewSQLLiteQuerier(dbPath) - if err != nil { - logrus.Fatal(err) - } - - m, err := ToModel(context.TODO(), store) - require.NoError(t, err) - require.NotNil(t, m) - require.NoError(t, m.Validate()) - require.Len(t, m, 3) - - require.Equal(t, "etcd", m["etcd"].Name) - require.NotNil(t, m["etcd"].Icon) - require.Equal(t, "alpha", m["etcd"].DefaultChannel.Name) - require.Len(t, m["etcd"].Channels, 3) - require.Len(t, m["etcd"].Channels["alpha"].Bundles, 3) - require.Len(t, m["etcd"].Channels["beta"].Bundles, 2) - require.Len(t, m["etcd"].Channels["stable"].Bundles, 3) - - require.Equal(t, "prometheus", m["prometheus"].Name) - require.NotNil(t, m["prometheus"].Icon) - require.Equal(t, "preview", m["prometheus"].DefaultChannel.Name) - require.Len(t, m["prometheus"].Channels, 1) - require.Len(t, m["prometheus"].Channels["preview"].Bundles, 3) - - require.Equal(t, "strimzi-kafka-operator", m["strimzi-kafka-operator"].Name) - require.NotNil(t, m["strimzi-kafka-operator"].Icon) - require.Equal(t, "stable", m["strimzi-kafka-operator"].DefaultChannel.Name) - require.Len(t, m["strimzi-kafka-operator"].Channels, 3) - require.Len(t, m["strimzi-kafka-operator"].Channels["alpha"].Bundles, 4) - require.Len(t, m["strimzi-kafka-operator"].Channels["beta"].Bundles, 3) - require.Len(t, m["strimzi-kafka-operator"].Channels["stable"].Bundles, 2) -} diff --git a/pkg/sqlite/db.go b/pkg/sqlite/db.go deleted file mode 100644 index 6426e6944..000000000 --- a/pkg/sqlite/db.go +++ /dev/null @@ -1,29 +0,0 @@ -package sqlite - -import ( - "database/sql" - - _ "github.com/mattn/go-sqlite3" -) - -// Open opens a connection to a sqlite db. It should be used everywhere instead of sql.Open so that foreign keys are -// ensured. -func Open(fileName string) (*sql.DB, error) { - return sql.Open("sqlite3", EnableForeignKeys(fileName)) -} - -// Open opens a connection to a sqlite db. It is -func OpenReadOnly(fileName string) (*sql.DB, error) { - return sql.Open("sqlite3", EnableImmutable(fileName)) -} - -// EnableForeignKeys appends the option to enable foreign keys on connections -// note that without this option, PRAGMAs about foreign keys will lie. -func EnableForeignKeys(fileName string) string { - return "file:" + fileName + "?_foreign_keys=on" -} - -// Immutable appends the option to mark the db immutable on connections -func EnableImmutable(fileName string) string { - return "file:" + fileName + "?immutable=true" -} diff --git a/pkg/sqlite/db_options.go b/pkg/sqlite/db_options.go deleted file mode 100644 index e09bfbc03..000000000 --- a/pkg/sqlite/db_options.go +++ /dev/null @@ -1,32 +0,0 @@ -package sqlite - -import ( - "database/sql" -) - -type DbOptions struct { - // MigratorBuilder is a function that returns a migrator instance - MigratorBuilder func(*sql.DB) (Migrator, error) - EnableAlpha bool -} - -type DbOption func(*DbOptions) - -func defaultDBOptions() *DbOptions { - return &DbOptions{ - MigratorBuilder: NewSQLLiteMigrator, - EnableAlpha: false, - } -} - -func WithMigratorBuilder(m func(loader *sql.DB) (Migrator, error)) DbOption { - return func(o *DbOptions) { - o.MigratorBuilder = m - } -} - -func WithEnableAlpha(enableAlpha bool) DbOption { - return func(o *DbOptions) { - o.EnableAlpha = enableAlpha - } -} diff --git a/pkg/sqlite/deprecate.go b/pkg/sqlite/deprecate.go deleted file mode 100644 index 80e11fc91..000000000 --- a/pkg/sqlite/deprecate.go +++ /dev/null @@ -1,147 +0,0 @@ -package sqlite - -import ( - "context" - "errors" - "fmt" - - "github.com/sirupsen/logrus" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type SQLDeprecator interface { - Deprecate() error -} - -// BundleDeprecator removes bundles from the database -type BundleDeprecator struct { - store registry.Load - bundles []string -} - -// PackageDeprecator removes bundles and optionally entire packages from the index -type PackageDeprecator struct { - *BundleDeprecator - querier *SQLQuerier -} - -var _ SQLDeprecator = &BundleDeprecator{} -var _ SQLDeprecator = &PackageDeprecator{} - -func NewSQLDeprecatorForBundles(store registry.Load, bundles []string) *BundleDeprecator { - return &BundleDeprecator{ - store: store, - bundles: bundles, - } -} - -func NewSQLDeprecatorForBundlesAndPackages(deprecator *BundleDeprecator, querier *SQLQuerier) *PackageDeprecator { - return &PackageDeprecator{ - BundleDeprecator: deprecator, - querier: querier, - } -} - -func (d *BundleDeprecator) Deprecate() error { - log := logrus.WithField("bundles", d.bundles) - log.Info("deprecating bundles") - - var errs []error - for _, bundlePath := range d.bundles { - if err := d.store.DeprecateBundle(bundlePath); err != nil && !errors.Is(err, registry.ErrBundleImageNotInDatabase) { - errs = append(errs, fmt.Errorf("error deprecating bundle %s: %s", bundlePath, err)) - if !errors.Is(err, registry.ErrRemovingDefaultChannelDuringDeprecation) { - break - } - } - } - - return utilerrors.NewAggregate(errs) -} - -// MaybeRemovePackages queries the DB to establish if any provided bundles are the head of the default channel of a package. -// If so, the list of bundles must also contain the head of all other channels in the package, otherwise an error is produced. -// If the heads of all channels are being deprecated (including the default channel), the package is removed entirely from the index. -// MaybeRemovePackages deletes all bundles from the associated package from the bundles array, so that the subsequent -// Deprecate() call can proceed with deprecating other potential bundles from other packages. -func (d *PackageDeprecator) MaybeRemovePackages() error { - log := logrus.WithField("bundles", d.bundles) - log.Info("allow-package-removal enabled: checking default channel heads for package removal") - - var errs []error - var removedBundlePaths []string - // nolint:prealloc - var remainingBundlePaths []string - - // Iterate over bundles list - see if any bundle is the head of a default channel in a package - var packages []string - for _, bundle := range d.bundles { - found, err := d.querier.PackageFromDefaultChannelHeadBundle(context.TODO(), bundle) - if err != nil { - errs = append(errs, fmt.Errorf("error checking if bundle is default channel head %s: %s", bundle, err)) - } - if found != "" { - packages = append(packages, found) - } - } - - if len(packages) == 0 { - log.Info("no head of default channel found - skipping package removal") - return nil - } - - // If so, ensure list contains head of all other channels in that package - // If not, return error - for _, pkg := range packages { - channels, err := d.querier.ListChannels(context.TODO(), pkg) - if err != nil { - errs = append(errs, fmt.Errorf("error listing channels for package %s: %s", pkg, err)) - } - for _, channel := range channels { - found, err := d.querier.BundlePathForChannelHead(context.TODO(), pkg, channel) - if err != nil { - errs = append(errs, fmt.Errorf("error listing channel head for package %s: %s", pkg, err)) - } - if !contains(found, d.bundles) { - // terminal error - errs = append(errs, fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", pkg, found, channel)) - return utilerrors.NewAggregate(errs) - } - removedBundlePaths = append(removedBundlePaths, found) - } - } - - // Remove associated package from index - log.Infof("removing packages %#v", packages) - for _, pkg := range packages { - err := d.store.RemovePackage(pkg) - if err != nil { - errs = append(errs, fmt.Errorf("error removing package %s: %s", pkg, err)) - } - } - - // Remove bundles from the removed package from the deprecation request - // This enables other bundles to be deprecated via the expected flow - // Build a new array with just the outstanding bundles - for _, bundlePath := range d.bundles { - if contains(bundlePath, removedBundlePaths) { - continue - } - remainingBundlePaths = append(remainingBundlePaths, bundlePath) - } - d.bundles = remainingBundlePaths - log.Infof("remaining bundles to deprecate %#v", d.bundles) - - return utilerrors.NewAggregate(errs) -} - -func contains(bundlePath string, bundles []string) bool { - for _, b := range bundles { - if b == bundlePath { - return true - } - } - return false -} diff --git a/pkg/sqlite/deprecationmessage.go b/pkg/sqlite/deprecationmessage.go deleted file mode 100644 index a0b4bc75f..000000000 --- a/pkg/sqlite/deprecationmessage.go +++ /dev/null @@ -1,19 +0,0 @@ -package sqlite - -import ( - "fmt" - - "github.com/sirupsen/logrus" -) - -const noticeColor = "\033[1;33m%s\033[0m" - -func LogSqliteDeprecation() { - log := logrus.New() - log.Warn(DeprecationMessage) -} - -var DeprecationMessage = fmt.Sprintf(noticeColor, `DEPRECATION NOTICE: -Sqlite-based catalogs and their related subcommands are deprecated. Support for -them will be removed in a future release. Please migrate your catalog workflows -to the new file-based catalog format.`) diff --git a/pkg/sqlite/directory.go b/pkg/sqlite/directory.go deleted file mode 100644 index a334ff693..000000000 --- a/pkg/sqlite/directory.go +++ /dev/null @@ -1,232 +0,0 @@ -package sqlite - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/yaml" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const ClusterServiceVersionKind = "ClusterServiceVersion" - -type SQLPopulator interface { - Populate() error -} - -// DirectoryLoader loads a directory of resources into the database -type DirectoryLoader struct { - store registry.Load - directory string -} - -var _ SQLPopulator = &DirectoryLoader{} - -func NewSQLLoaderForDirectory(store registry.Load, directory string) *DirectoryLoader { - return &DirectoryLoader{ - store: store, - directory: directory, - } -} - -func (d *DirectoryLoader) Populate() error { - log := logrus.WithField("dir", d.directory) - - log.Info("loading Bundles") - errs := make([]error, 0) - if err := filepath.Walk(d.directory, collectWalkErrs(d.LoadBundleWalkFunc, &errs)); err != nil { - errs = append(errs, err) - } - - log.Info("loading Packages and Entries") - if err := filepath.Walk(d.directory, collectWalkErrs(d.LoadPackagesWalkFunc, &errs)); err != nil { - errs = append(errs, err) - } - - return utilerrors.NewAggregate(errs) -} - -// collectWalkErrs calls the given walk func and appends any non-nil, non skip dir error returned to the given errors slice. -func collectWalkErrs(walk filepath.WalkFunc, errs *[]error) filepath.WalkFunc { - return func(path string, f os.FileInfo, err error) error { - var walkErr error - // nolint: errorlint - if walkErr = walk(path, f, err); walkErr != nil && walkErr != filepath.SkipDir { - *errs = append(*errs, walkErr) - return nil - } - - return walkErr - } -} - -// LoadBundleWalkFunc walks the directory. When it sees a `.clusterserviceversion.yaml` file, it -// attempts to load the surrounding files in the same directory as a bundle, and stores them in the -// db for querying -func (d *DirectoryLoader) LoadBundleWalkFunc(path string, f os.FileInfo, _ error) error { - if f == nil { - return fmt.Errorf("invalid file: %v", f) - } - - log := logrus.WithFields(logrus.Fields{"dir": d.directory, "file": f.Name(), "load": "bundles"}) - if f.IsDir() { - if strings.HasPrefix(f.Name(), ".") { - log.Info("skipping hidden directory") - return filepath.SkipDir - } - log.Info("directory") - return nil - } - - if strings.HasPrefix(f.Name(), ".") { - log.Info("skipping hidden file") - return nil - } - - fileReader, err := os.Open(path) - if err != nil { - return fmt.Errorf("unable to load file %s: %s", path, err) - } - defer fileReader.Close() - - decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - csv := unstructured.Unstructured{} - - if err = decoder.Decode(&csv); err != nil { - return nil - } - - if csv.GetKind() != ClusterServiceVersionKind { - return nil - } - - log.Info("found csv, loading bundle") - - var errs []error - bundle, err := loadBundle(csv.GetName(), filepath.Dir(path)) - if err != nil { - errs = append(errs, fmt.Errorf("error loading objs in directory: %s", err)) - } - - if bundle == nil || bundle.Size() == 0 { - errs = append(errs, fmt.Errorf("no bundle objects found")) - return utilerrors.NewAggregate(errs) - } - - if err := bundle.AllProvidedAPIsInBundle(); err != nil { - errs = append(errs, fmt.Errorf("error checking provided apis in bundle %s: %s", bundle.Name, err)) - } - - if err := d.store.AddOperatorBundle(bundle); err != nil { - version, _ := bundle.Version() - errs = append(errs, fmt.Errorf("error adding operator bundle %s/%s/%s: %s", csv.GetName(), version, bundle.BundleImage, err)) - } - - return utilerrors.NewAggregate(errs) -} - -// LoadPackagesWalkFunc attempts to unmarshal the file at the given path into a PackageManifest resource. -// If unmarshaling is successful, the PackageManifest is added to the loader's store. -func (d *DirectoryLoader) LoadPackagesWalkFunc(path string, f os.FileInfo, _ error) error { - if f == nil { - return fmt.Errorf("invalid file: %v", f) - } - - log := logrus.WithFields(logrus.Fields{"dir": d.directory, "file": f.Name(), "load": "package"}) - if f.IsDir() { - if strings.HasPrefix(f.Name(), ".") { - log.Info("skipping hidden directory") - return filepath.SkipDir - } - log.Info("directory") - return nil - } - - if strings.HasPrefix(f.Name(), ".") { - log.Info("skipping hidden file") - return nil - } - - fileReader, err := os.Open(path) - if err != nil { - return fmt.Errorf("unable to load package from file %s: %s", path, err) - } - defer fileReader.Close() - - decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - manifest := registry.PackageManifest{} - if err = decoder.Decode(&manifest); err != nil { - if err != nil { - return fmt.Errorf("could not decode contents of file %s into package: %s", path, err) - } - } - if manifest.PackageName == "" { - return nil - } - - if err := d.store.AddPackageChannels(manifest); err != nil { - return fmt.Errorf("error loading package into db: %s", err) - } - - return nil -} - -// loadBundle takes the directory that a CSV is in and assumes the rest of the objects in that directory -// are part of the bundle. -func loadBundle(csvName string, dir string) (*registry.Bundle, error) { - log := logrus.WithFields(logrus.Fields{"dir": dir, "load": "bundle", "name": csvName}) - files, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - - var errs []error - bundle := ®istry.Bundle{ - Name: csvName, - } - for _, f := range files { - log = log.WithField("file", f.Name()) - if f.IsDir() { - log.Info("skipping directory") - continue - } - - if strings.HasPrefix(f.Name(), ".") { - log.Info("skipping hidden file") - continue - } - - log.Info("loading bundle file") - path := filepath.Join(dir, f.Name()) - fileReader, err := os.Open(path) - if err != nil { - errs = append(errs, fmt.Errorf("unable to load file %s: %s", path, err)) - continue - } - defer fileReader.Close() - - decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - obj := &unstructured.Unstructured{} - if err = decoder.Decode(obj); err != nil { - logrus.WithError(err).Debugf("could not decode file contents for %s", path) - continue - } - - // Don't include other CSVs in the bundle - if obj.GetKind() == "ClusterServiceVersion" && obj.GetName() != csvName { - continue - } - - if obj.Object != nil { - bundle.Add(obj) - } - } - - return bundle, utilerrors.NewAggregate(errs) -} diff --git a/pkg/sqlite/directory_test.go b/pkg/sqlite/directory_test.go deleted file mode 100644 index 767d74334..000000000 --- a/pkg/sqlite/directory_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package sqlite - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/otiai10/copy" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func TestDirectoryLoader(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - loader := NewSQLLoaderForDirectory(store, "./testdata/loader_data") - require.NoError(t, loader.Populate()) -} - -func TestDirectoryLoaderWithBadPackageData(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - // Copy golden manifests to a temp dir - dir := t.TempDir() - require.NoError(t, copy.Copy("./testdata/loader_data", dir)) - - // Point the first channel at a CSV that doesn't exist - path := filepath.Join(dir, "etcd/etcd.package.yaml") - r, err := os.Open(path) - require.NoError(t, err) - - pkg := new(registry.PackageManifest) - require.NoError(t, yaml.NewDecoder(r).Decode(pkg)) - require.Greater(t, len(pkg.Channels), 1) - pkg.Channels[0].CurrentCSVName = "imaginary" - - // Replace file contents - w, err := os.Create(path) - require.NoError(t, err) - require.NoError(t, yaml.NewEncoder(w).Encode(pkg)) - - // Load and expect error - loader := NewSQLLoaderForDirectory(store, dir) - require.Error(t, loader.Populate(), "error loading package into db: no bundle found for csv imaginary") -} - -func TestDirectoryLoaderWithBadBundleData(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - // Load and expect error - // incorrectbundle has an operator which has incorrect data - // (a number where a string is expected) in it's CSV - loader := NewSQLLoaderForDirectory(store, "pkg/sqlite/testdata/incorrectbundle") - require.Error(t, loader.Populate(), "error loading manifests from directory: [error adding operator bundle : json: cannot unmarshal number into Go struct field EnvVar.Install.spec.Deployments.Spec.template.spec.containers.env.value of type string, error loading package into db: [FOREIGN KEY constraint failed, no bundle found for csv 3scale-community-operator.v0.3.0]]") -} - -func TestQuerierForDirectory(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - load, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, load.Migrate(context.TODO())) - - loader := NewSQLLoaderForDirectory(load, "../../manifests") - require.NoError(t, loader.Populate()) - - store := NewSQLLiteQuerierFromDb(db) - foundPackages, err := store.ListPackages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{"etcd", "prometheus", "strimzi-kafka-operator"}, foundPackages) - - etcdPackage, err := store.GetPackage(context.TODO(), "etcd") - require.NoError(t, err) - require.Equal(t, ®istry.PackageManifest{ - PackageName: "etcd", - DefaultChannelName: "alpha", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "etcdoperator.v0.9.2", - }, - { - Name: "beta", - CurrentCSVName: "etcdoperator.v0.9.0", - }, - { - Name: "stable", - CurrentCSVName: "etcdoperator.v0.9.2", - }, - }, - }, etcdPackage) - - etcdBundleByChannel, err := store.GetBundleForChannel(context.TODO(), "etcd", "alpha") - require.NoError(t, err) - expectedBundle := &api.Bundle{ - CsvName: "etcdoperator.v0.9.2", - PackageName: "etcd", - ChannelName: "alpha", - Version: "0.9.2", - SkipRange: "< 0.6.0", - 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 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 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\":\"iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC\",\"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: "", - Dependencies: []*api.Dependency{ - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - }, - Properties: []*api.Property{ - { - Type: "olm.package", - Value: `{"packageName":"etcd","version":"0.9.2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdCluster","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdBackup","version":"v1beta2"}`, - }, - { - Type: "olm.gvk", - Value: `{"group":"etcd.database.coreos.com","kind":"EtcdRestore","version":"v1beta2"}`, - }, - { - Type: "olm.label", - Value: `{"label":"testlabel"}`, - }, - { - Type: "olm.label", - Value: `{"label":"testlabel1"}`, - }, - { - Type: "other", - Value: `{"its":"notdefined"}`, - }, - }, - ProvidedApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdBackup", Plural: "etcdbackups"}, - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdRestore", Plural: "etcdrestores"}, - }, - RequiredApis: []*api.GroupVersionKind{ - {Group: "etcd.database.coreos.com", Version: "v1beta2", Kind: "EtcdCluster", Plural: "etcdclusters"}, - }, - } - bareGetBundleForChannelResult := &api.Bundle{ - CsvName: expectedBundle.CsvName, - CsvJson: expectedBundle.CsvJson + "\n", - } - EqualBundles(t, *bareGetBundleForChannelResult, *etcdBundleByChannel) - - etcdBundle, err := store.GetBundle(context.TODO(), "etcd", "alpha", "etcdoperator.v0.9.2") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundle) - - etcdChannelEntries, err := store.GetChannelEntriesThatReplace(context.TODO(), "etcdoperator.v0.9.0") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdChannelEntries) - - etcdBundleByReplaces, err := store.GetBundleThatReplaces(context.TODO(), "etcdoperator.v0.9.0", "etcd", "alpha") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByReplaces) - - etcdChannelEntriesThatProvide, err := store.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - for _, c := range etcdChannelEntriesThatProvide { - t.Logf("%#v", c) - } - require.ElementsMatch(t, []*registry.ChannelEntry{ - {"etcd", "alpha", "etcdoperator.v0.6.1", ""}, - {"etcd", "alpha", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"}, - {"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.1"}, - {"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "beta", "etcdoperator.v0.6.1", ""}, - {"etcd", "beta", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"}, - {"etcd", "stable", "etcdoperator.v0.6.1", ""}, - {"etcd", "stable", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.1"}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdChannelEntriesThatProvide) - - etcdLatestChannelEntriesThatProvide, err := store.GetLatestChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - require.ElementsMatch(t, []*registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}, - {"etcd", "beta", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"}, - {"etcd", "stable", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdLatestChannelEntriesThatProvide) - - etcdBundleByProvides, err := store.GetBundleThatProvides(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdCluster") - require.NoError(t, err) - EqualBundles(t, *expectedBundle, *etcdBundleByProvides) - - kafkaPackage, err := store.GetPackage(context.TODO(), "strimzi-kafka-operator") - require.NoError(t, err) - expectedKafkaPackage := ®istry.PackageManifest{ - PackageName: "strimzi-kafka-operator", - DefaultChannelName: "stable", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "strimzi-cluster-operator.v0.11.1", - }, - { - Name: "beta", - CurrentCSVName: "strimzi-cluster-operator.v0.12.1", - }, - { - Name: "alpha", - CurrentCSVName: "strimzi-cluster-operator.v0.12.2", - }, - }, - } - require.Equal(t, expectedKafkaPackage.PackageName, kafkaPackage.PackageName) - require.Equal(t, expectedKafkaPackage.DefaultChannelName, kafkaPackage.DefaultChannelName) - require.ElementsMatch(t, expectedKafkaPackage.Channels, kafkaPackage.Channels) - - expectedEtcdImages := []string{ - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - "quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84", - "quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f", - } - etcdImages, err := store.GetImagesForBundle(context.TODO(), "etcdoperator.v0.9.2") - require.NoError(t, err) - require.ElementsMatch(t, expectedEtcdImages, etcdImages) - - expectedDatabaseImages := []string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - "quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8", - "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", - "quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84", - "quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f", - "quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731", - "quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d", - "quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf", - "strimzi/cluster-operator:0.11.0", - "strimzi/cluster-operator:0.11.1", - "strimzi/operator:0.12.1", - "strimzi/operator:0.12.2", - } - dbImages, err := store.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, expectedDatabaseImages, dbImages) -} - -func EqualBundles(t *testing.T, expected, actual api.Bundle) { - require.ElementsMatch(t, expected.ProvidedApis, actual.ProvidedApis) - require.ElementsMatch(t, expected.RequiredApis, actual.RequiredApis) - require.ElementsMatch(t, expected.Dependencies, actual.Dependencies) - require.ElementsMatch(t, expected.Properties, actual.Properties) - expected.RequiredApis, expected.ProvidedApis, actual.RequiredApis, actual.ProvidedApis = nil, nil, nil, nil - expected.Dependencies, expected.Properties, actual.Dependencies, actual.Properties = nil, nil, nil, nil - require.Equal(t, expected, actual) -} diff --git a/pkg/sqlite/graphloader.go b/pkg/sqlite/graphloader.go deleted file mode 100644 index e89bd75cd..000000000 --- a/pkg/sqlite/graphloader.go +++ /dev/null @@ -1,144 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "fmt" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type SQLGraphLoader struct { - Querier registry.Query -} - -func NewSQLGraphLoader(dbFilename string) (*SQLGraphLoader, error) { - querier, err := NewSQLLiteQuerier(dbFilename) - if err != nil { - return nil, err - } - - return &SQLGraphLoader{ - Querier: querier, - }, nil -} - -func NewSQLGraphLoaderFromDB(db *sql.DB) (*SQLGraphLoader, error) { - return &SQLGraphLoader{ - Querier: NewSQLLiteQuerierFromDb(db), - }, nil -} - -func (g *SQLGraphLoader) Generate(packageName string) (*registry.Package, error) { - graph := ®istry.Package{ - Name: packageName, - Channels: make(map[string]registry.Channel, 0), - } - - ctx := context.TODO() - defaultChannel, err := g.Querier.GetDefaultPackage(ctx, packageName) - if err != nil { - return graph, registry.ErrPackageNotInDatabase - } - graph.DefaultChannel = defaultChannel - - channelEntries, err := g.Querier.GetChannelEntriesFromPackage(ctx, packageName) - if err != nil { - return graph, err - } - - existingBundles, err := g.Querier.GetBundlesForPackage(ctx, packageName) - if err != nil { - return graph, err - } - - channels, err := graphFromEntries(channelEntries, existingBundles) - if err != nil { - return graph, err - } - graph.Channels = channels - - return graph, nil -} - -// graphFromEntries builds the graph from a set of channel entries -func graphFromEntries(channelEntries []registry.ChannelEntryAnnotated, existingBundles map[registry.BundleKey]struct{}) (map[string]registry.Channel, error) { - channels := map[string]registry.Channel{} - - type replaces map[registry.BundleKey]map[registry.BundleKey]struct{} - - channelGraph := map[string]replaces{} - channelHeadCandidates := map[string]map[registry.BundleKey]struct{}{} - - // add all channels and nodes to the graph - for _, entry := range channelEntries { - // create channel if we haven't seen it yet - if _, ok := channelGraph[entry.ChannelName]; !ok { - channelGraph[entry.ChannelName] = replaces{} - } - - key := registry.BundleKey{ - BundlePath: entry.BundlePath, - Version: entry.Version, - CsvName: entry.BundleName, - } - - // skip synthetic channelentries that aren't pointers to actual bundles - if _, ok := existingBundles[key]; !ok { - continue - } - - channelGraph[entry.ChannelName][key] = map[registry.BundleKey]struct{}{} - - // every bundle in a channel is a potential head of that channel - if _, ok := channelHeadCandidates[entry.ChannelName]; !ok { - channelHeadCandidates[entry.ChannelName] = map[registry.BundleKey]struct{}{key: {}} - } else { - channelHeadCandidates[entry.ChannelName][key] = struct{}{} - } - } - - for _, entry := range channelEntries { - key := registry.BundleKey{ - BundlePath: entry.BundlePath, - Version: entry.Version, - CsvName: entry.BundleName, - } - replacesKey := registry.BundleKey{ - BundlePath: entry.ReplacesBundlePath, - Version: entry.ReplacesVersion, - CsvName: entry.Replaces, - } - - if !replacesKey.IsEmpty() { - if _, ok := channelGraph[entry.ChannelName]; !ok { - channelGraph[entry.ChannelName] = replaces{key: {replacesKey: struct{}{}}} - } - if _, ok := channelGraph[entry.ChannelName][key]; !ok { - channelGraph[entry.ChannelName][key] = map[registry.BundleKey]struct{}{replacesKey: {}} - } - channelGraph[entry.ChannelName][key][replacesKey] = struct{}{} - } - - delete(channelHeadCandidates[entry.ChannelName], replacesKey) - } - - for channelName, candidates := range channelHeadCandidates { - if len(candidates) == 0 { - return nil, fmt.Errorf("no channel head found for %s", channelName) - } - if len(candidates) > 1 { - return nil, fmt.Errorf("multiple candidate channel heads found for %s: %v", channelName, candidates) - } - - for head := range candidates { - channel := registry.Channel{ - Head: head, - Nodes: channelGraph[channelName], - } - channels[channelName] = channel - } - } - - return channels, nil -} diff --git a/pkg/sqlite/graphloader_test.go b/pkg/sqlite/graphloader_test.go deleted file mode 100644 index b6f4cdac7..000000000 --- a/pkg/sqlite/graphloader_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func createLoadedTestDB(t *testing.T) (*sql.DB, func()) { - db, cleanup := CreateTestDB(t) - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - loader := NewSQLLoaderForDirectory(store, "./testdata/loader_data") - require.NoError(t, loader.Populate()) - - return db, cleanup -} - -func TestLoadPackageGraph_Etcd(t *testing.T) { - expectedGraph := ®istry.Package{ - Name: "etcd", - DefaultChannel: "alpha", - Channels: map[string]registry.Channel{ - "alpha": { - Head: registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, - {BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { - registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, - }, - {BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}: { - registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: struct{}{}, - registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: struct{}{}, - }, - }, - }, - "beta": { - Head: registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, - {BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { - registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, - }, - }, - }, - "stable": { - Head: registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}, - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - {BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, - {BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { - registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, - }, - {BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}: { - registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: struct{}{}, - registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: struct{}{}, - }, - }, - }, - }, - } - - db, cleanup := createLoadedTestDB(t) - defer cleanup() - - graphLoader, err := NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - result, err := graphLoader.Generate("etcd") - require.NoError(t, err) - - require.Equal(t, "etcd", result.Name) - require.Len(t, result.Channels, 3) - - for channelName, channel := range result.Channels { - expectedChannel := expectedGraph.Channels[channelName] - require.Equal(t, expectedChannel.Head, channel.Head) - require.Equal(t, expectedChannel.Nodes, channel.Nodes) - } -} - -func TestLoadPackageGraph_Etcd_NotFound(t *testing.T) { - db, cleanup := createLoadedTestDB(t) - defer cleanup() - - graphLoader, err := NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - _, err = graphLoader.Generate("not-a-real-package") - require.Error(t, err) - require.Equal(t, registry.ErrPackageNotInDatabase, err) -} diff --git a/pkg/sqlite/load.go b/pkg/sqlite/load.go deleted file mode 100644 index 190144eaf..000000000 --- a/pkg/sqlite/load.go +++ /dev/null @@ -1,1867 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" - - "github.com/blang/semver/v4" - _ "github.com/mattn/go-sqlite3" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - libsemver "github.com/operator-framework/operator-registry/pkg/lib/semver" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type sqlLoader struct { - db *sql.DB - migrator Migrator - enableAlpha bool -} - -type MigratableLoader interface { - registry.Load - Migrate(context.Context) error -} - -var _ MigratableLoader = &sqlLoader{} - -// startDepth is the depth that channel heads should be assigned -// in the channel_entry table. This const exists so that all -// add modes (replaces, semver, and semver-skippatch) are -// consistent. -const startDepth = 0 - -func newSQLLoader(db *sql.DB, opts ...DbOption) (*sqlLoader, error) { - options := defaultDBOptions() - for _, o := range opts { - o(options) - } - - if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { - return nil, err - } - - migrator, err := options.MigratorBuilder(db) - if err != nil { - return nil, err - } - - return &sqlLoader{db: db, migrator: migrator, enableAlpha: options.EnableAlpha}, nil -} - -func NewSQLLiteLoader(db *sql.DB, opts ...DbOption) (MigratableLoader, error) { - return newSQLLoader(db, opts...) -} - -func (s *sqlLoader) Migrate(ctx context.Context) error { - if s.migrator == nil { - return fmt.Errorf("no migrator configured") - } - return s.migrator.Migrate(ctx) -} - -func (s *sqlLoader) AddOperatorBundle(bundle *registry.Bundle) error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - if err := s.addOperatorBundle(tx, bundle); err != nil { - return err - } - - return tx.Commit() -} - -func (s *sqlLoader) addOperatorBundle(tx *sql.Tx, bundle *registry.Bundle) error { - addBundle, err := tx.Prepare("insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips, substitutesfor) values(?, ?, ?, ?, ?, ?, ?, ?, ?)") - if err != nil { - return err - } - defer addBundle.Close() - - addImage, err := tx.Prepare("insert into related_image(image, operatorbundle_name) values(?,?)") - if err != nil { - return fmt.Errorf("failed to insert related image: %s", err) - } - defer addImage.Close() - - csvName, bundleImage, csvBytes, bundleBytes, _, err := bundle.Serialize() - if err != nil { - return fmt.Errorf("unable to serialize the bundle : %s", err) - } - - if csvName == "" { - return fmt.Errorf("csv name not found") - } - - version, err := bundle.Version() - if err != nil { - return fmt.Errorf("unable to obtain bundle version : %s", err) - } - skiprange, err := bundle.SkipRange() - if err != nil { - return fmt.Errorf("unable to obtain skipRange : %s", err) - } - replaces, err := bundle.Replaces() - if err != nil { - return fmt.Errorf("unable to obtain replaces : %s", err) - } - skips, err := bundle.Skips() - if err != nil { - return fmt.Errorf("unable to obtain skips : %s", err) - } - substitutesFor, err := bundle.SubstitutesFor() - if err != nil { - return fmt.Errorf("unable to obtain substitutes : %s", err) - } - - if substitutesFor != "" && !s.enableAlpha { - //nolint:staticcheck // ST1005: error message intentionally ends with punctuation - return fmt.Errorf("SubstitutesFor is an alpha-only feature. You must enable alpha features with the flag --enable-alpha in order to use this feature.") - } - - if _, err := addBundle.Exec(csvName, csvBytes, bundleBytes, bundleImage, version, skiprange, replaces, strings.Join(skips, ","), substitutesFor); err != nil { - return fmt.Errorf("failed to add bundle %q: %s", csvName, err.Error()) - } - - imgs, err := bundle.Images() - if err != nil { - return fmt.Errorf("unable to obtain images : %s", err) - } - for img := range imgs { - if _, err := addImage.Exec(img, csvName); err != nil { - return fmt.Errorf("failed to add related images %q for bundle %q: %s", img, csvName, err.Error()) - } - } - - // Add dependencies information - err = s.addDependencies(tx, bundle) - if err != nil { - return fmt.Errorf("failed to add dependencies : %s", err) - } - - err = s.addBundleProperties(tx, bundle) - if err != nil { - return fmt.Errorf("failed to add properties : %s", err) - } - - if s.enableAlpha { - err = s.addSubstitutesFor(tx, bundle) - if err != nil { - return fmt.Errorf("failed to add substitutes : %s", err) - } - } - - return s.addAPIs(tx, bundle) -} - -func (s *sqlLoader) addSubstitutesFor(tx *sql.Tx, bundle *registry.Bundle) error { - updateBundleReplaces, err := tx.Prepare("update operatorbundle set replaces = ? where replaces = ?") - if err != nil { - return err - } - defer updateBundleReplaces.Close() - - updateBundleSkips, err := tx.Prepare("update operatorbundle set skips = ? where name = ?") - if err != nil { - return err - } - defer updateBundleSkips.Close() - - updateBundleSubstitutesFor, err := tx.Prepare("update operatorbundle set substitutesfor = ? where name = ?") - if err != nil { - return err - } - defer updateBundleSubstitutesFor.Close() - - updateBundleReplacesSkips, err := tx.Prepare("update operatorbundle set replaces = ?, skips = ? where name = ?") - if err != nil { - return err - } - defer updateBundleReplacesSkips.Close() - - csvName := bundle.Name - - replaces, err := bundle.Replaces() - if err != nil { - return fmt.Errorf("failed to obtain replaces : %s", err) - } - skips, err := bundle.Skips() - if err != nil { - return fmt.Errorf("failed to obtain skips : %s", err) - } - version, err := bundle.Version() - if err != nil { - return fmt.Errorf("failed to obtain version : %s", err) - } - substitutesFor, err := bundle.SubstitutesFor() - if err != nil { - return fmt.Errorf("failed to obtain substitutes : %s", err) - } - // nolint:nestif - if substitutesFor != "" { - // Update any replaces that reference the substituted-for bundle - _, err = updateBundleReplaces.Exec(csvName, substitutesFor) - if err != nil { - return err - } - // Check if any other bundle substitutes for the same bundle - otherSubstitutions, err := s.getBundlesThatSubstitutesFor(tx, substitutesFor) - if err != nil { - return err - } - for len(otherSubstitutions) > 0 { - // consume the slice of substitutions - otherSubstitution := otherSubstitutions[0] - otherSubstitutions = otherSubstitutions[1:] - if otherSubstitution != csvName { - // Another bundle is substituting for that same bundle - // Get other bundle's version - _, _, rawVersion, err := s.getBundleSkipsReplacesVersion(tx, otherSubstitution) - if err != nil { - return err - } - otherSubstitutionVersion, err := semver.Parse(rawVersion) - if err != nil { - return err - } - currentSubstitutionVersion, err := semver.Parse(version) - if err != nil { - return err - } - // Compare versions - c, err := libsemver.BuildIdCompare(otherSubstitutionVersion, currentSubstitutionVersion) - if err != nil { - return err - } - if c < 0 { - // Update the currentSubstitution substitutesFor to point to otherSubstitution - // since it is latest - _, err = updateBundleSubstitutesFor.Exec(otherSubstitution, csvName) - if err != nil { - return err - } - moreSubstitutions, err := s.getBundlesThatSubstitutesFor(tx, otherSubstitution) - if err != nil { - return err - } - otherSubstitutions = append(otherSubstitutions, moreSubstitutions...) - } else if c > 0 { - // Update the otherSubstitution's substitutesFor to point to csvName - // Since it is the latest - _, err = updateBundleSubstitutesFor.Exec(csvName, otherSubstitution) - if err != nil { - return err - } - // Update the otherSubstitution's skips to include csvName and its skips - err = s.appendSkips(tx, append(skips, csvName), otherSubstitution) - if err != nil { - return err - } - moreSubstitutions, err := s.getBundlesThatSubstitutesFor(tx, csvName) - if err != nil { - return err - } - if len(moreSubstitutions) > 1 { - return fmt.Errorf("programmer error: more than one substitution pointing to %s", csvName) - } - } else { - // the versions are equal - return fmt.Errorf("cannot determine latest substitution because of duplicate versions") - } - } - } - } - - // Get latest substitutesFor value of the current bundle - substitutesFor, err = s.getBundleSubstitution(tx, csvName) - if err != nil { - return err - } - - // If the substituted-for of the current bundle substitutes for another bundle - // it should also be added to the skips of the substitutesFor bundle - for substitutesFor != "" { - skips = append(skips, substitutesFor) - substitutesFor, err = s.getBundleSubstitution(tx, substitutesFor) - if err != nil { - return err - } - } - - // If the substitution (or substitution of substitution) is added before the - // substituted for bundle, (i.e. the bundle being added is substituted for by - // another bundle) then transfer the skips from the substitutedFor bundle (this - // bundle) over to the substitution's skips - var substitutesFors []string - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, csvName) - if err != nil || len(substitutesFors) > 1 { - return err - } - for len(substitutesFors) > 0 { - err = s.appendSkips(tx, append(skips, csvName), substitutesFors[0]) - if err != nil { - return err - } - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, substitutesFors[0]) - if err != nil || len(substitutesFors) > 1 { - return err - } - } - - // Bundles that skip a bundle that is substituted for - // should also skip the substituted-for bundle - if len(skips) != 0 { - // ensure slice of skips doesn't contain duplicates - substitutesSkips := make(map[string]struct{}) - skipsOverwrite := []string{} - for _, skip := range skips { - substitutesSkips[skip] = struct{}{} - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, skip) - if err != nil || len(substitutesFors) > 1 { - return err - } - for len(substitutesFors) > 0 { - // consume the slice of substitutions - substitutesFor = substitutesFors[0] - substitutesFors = substitutesFors[1:] - // shouldn't skip yourself - if substitutesFor != csvName { - substitutesSkips[substitutesFor] = struct{}{} - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, substitutesFor) - if err != nil || len(substitutesFors) > 1 { - return err - } - } - } - } - for s := range substitutesSkips { - skipsOverwrite = append(skipsOverwrite, s) - } - skips = skipsOverwrite - } - - // If the bundle being added replaces a bundle that is substituted for - // (for example it was the previous head of the channel), change - // the replaces to the substituted-for bundle - if replaces != "" { - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, replaces) - if err != nil { - return err - } - for len(substitutesFors) > 0 { - // update the replaces to a newer substitution - replaces = substitutesFors[0] - // try to get the substitution of the substitution - substitutesFors, err = s.getBundlesThatSubstitutesFor(tx, replaces) - if err != nil || len(substitutesFors) > 1 { - return err - } - } - } - - _, err = updateBundleReplacesSkips.Exec(replaces, strings.Join(skips, ","), csvName) - if err != nil { - return err - } - - return nil -} - -func (s *sqlLoader) appendSkips(tx *sql.Tx, skips []string, csvName string) error { - updateSkips, err := tx.Prepare("update operatorbundle set skips = ? where name = ?") - if err != nil { - return err - } - defer updateSkips.Close() - - _, currentSkips, _, err := s.getBundleSkipsReplacesVersion(tx, csvName) - if err != nil { - return err - } - - // ensure slice of skips doesn't contain duplicates - skipsMap := make(map[string]struct{}) - for _, skip := range currentSkips { - skipsMap[skip] = struct{}{} - } - for _, skip := range skips { - if _, ok := skipsMap[skip]; !ok { - currentSkips = append(currentSkips, skip) - } - } - - _, err = updateSkips.Exec(strings.Join(currentSkips, ","), csvName) - return err -} - -func (s *sqlLoader) AddPackageChannelsFromGraph(graph *registry.Package) error { - tx, err := s.db.Begin() - if err != nil { - return fmt.Errorf("unable to start a transaction: %s", err) - } - defer func() { - _ = tx.Rollback() - }() - - var errs []error - - if err := addPackageIfNotExists(tx, graph.Name); err != nil { - errs = append(errs, err) - } - - for name, channel := range graph.Channels { - if err := addOrUpdateChannel(tx, name, graph.Name, channel.Head.CsvName); err != nil { - errs = append(errs, err) - continue - } - } - - if err := updateDefaultChannel(tx, graph.DefaultChannel, graph.Name); err != nil { - errs = append(errs, fmt.Errorf("the default channel (%s) does not exist: %s", graph.DefaultChannel, err)) - } - - // update each channel's graph - for channelName, channel := range graph.Channels { - currentNode := channel.Head - depth := startDepth - - var previousNodeID int64 - - // first clear the current channel graph - err := truncChannelGraph(tx, channelName, graph.Name) - if err != nil { - errs = append(errs, err) - break - } - - // iterate into the replacement chain of the channel to insert or update all entries - for { - // create real channel entry for node - id, err := addChannelEntry(tx, channelName, graph.Name, currentNode.CsvName, depth) - if err != nil { - errs = append(errs, err) - break - } - - // If the previous node was created, use the entryId of the current node to update - // the replaces for the previous node - if previousNodeID != 0 { - err := addReplaces(tx, id, previousNodeID) - if err != nil { - errs = append(errs, err) - } - } - - syntheticReplaces := make([]registry.BundleKey, 0) // for CSV skips - nextNode := registry.BundleKey{} - - currentNodeReplaces := channel.Nodes[currentNode] - - // Iterate over all replaces for the node in the graph - // It should only contain one real replacement, so let's find it and - // follow the chain. For the rest, they are fake entries and should be - // generated as synthetic replacements - for replace := range currentNodeReplaces { - if _, ok := channel.Nodes[replace]; !ok { - syntheticReplaces = append(syntheticReplaces, replace) - } else { - nextNode = replace - } - } - - // create synthetic channel entries for nodes - // also create channel entry to replace that node - syntheticDepth := depth + 1 - for _, synthetic := range syntheticReplaces { - syntheticReplacesID, err := addChannelEntry(tx, channelName, graph.Name, synthetic.CsvName, syntheticDepth) - if err != nil { - errs = append(errs, err) - break - } - - syntheticNodeID, err := addChannelEntry(tx, channelName, graph.Name, currentNode.CsvName, syntheticDepth) - if err != nil { - errs = append(errs, err) - break - } - - err = addReplaces(tx, syntheticReplacesID, syntheticNodeID) - if err != nil { - errs = append(errs, err) - } - syntheticDepth++ - } - - // we got to the end of the channel graph - if nextNode.IsEmpty() { - // expectedDepth is: - // + - 1 - // For example, if the number of nodes is 3 and the startDepth is 0, the expected depth is 2 (0, 1, 2) - // If the number of nodes is 5 and the startDepth is 3, the expected depth is 7 (3, 4, 5, 6, 7) - expectedDepth := len(channel.Nodes) + startDepth - 1 - if expectedDepth != depth { - //nolint:staticcheck // ST1005: error message is intentionally capitalized - err := fmt.Errorf("Invalid graph: some (non-bottom) nodes defined in the graph were not mentioned as replacements of any node (%d != %d)", expectedDepth, depth) - errs = append(errs, err) - } - break - } - - // increase depth and continue - currentNode = nextNode - previousNodeID = id - depth++ - } - } - - if err := tx.Commit(); err != nil { - errs = append(errs, err) - } - - return utilerrors.NewAggregate(errs) -} - -func (s *sqlLoader) AddPackageChannels(manifest registry.PackageManifest) error { - tx, err := s.db.Begin() - if err != nil { - return fmt.Errorf("unable to start a transaction: %s", err) - } - defer func() { - _ = tx.Rollback() - }() - - if err := s.rmPackage(tx, manifest.PackageName); err != nil { - return err - } - - if err := s.addPackageChannels(tx, manifest); err != nil { - return err - } - - return tx.Commit() -} - -func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageManifest) error { - addPackage, err := tx.Prepare("insert into package(name) values(?)") - if err != nil { - return err - } - defer addPackage.Close() - - addDefaultChannel, err := tx.Prepare("update package set default_channel = ? where name = ?") - if err != nil { - return err - } - defer addDefaultChannel.Close() - - addChannel, err := tx.Prepare("insert into channel(name, package_name, head_operatorbundle_name) values(?, ?, ?)") - if err != nil { - return err - } - defer addChannel.Close() - - addChannelEntry, err := tx.Prepare("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)") - if err != nil { - return err - } - defer addChannelEntry.Close() - - addReplaces, err := tx.Prepare("update channel_entry set replaces = ? where entry_id = ?") - if err != nil { - return err - } - defer addReplaces.Close() - - getReplaces, err := tx.Prepare(` - SELECT DISTINCT operatorbundle.csv - FROM operatorbundle - WHERE operatorbundle.name=? LIMIT 1`) - if err != nil { - return err - } - defer getReplaces.Close() - - if _, err := addPackage.Exec(manifest.PackageName); err != nil { - return fmt.Errorf("failed to add package %q: %s", manifest.PackageName, err.Error()) - } - - // nolint:prealloc - var ( - errs []error - channels []registry.PackageChannel - hasDefault bool - ) - for _, c := range manifest.Channels { - if deprecated, err := s.deprecated(tx, c.CurrentCSVName); err != nil || deprecated { - // Elide channels that start with a deprecated bundle - continue - } - if _, err := addChannel.Exec(c.Name, manifest.PackageName, c.CurrentCSVName); err != nil { - errs = append(errs, fmt.Errorf("failed to add channel %q in package %q: %s", c.Name, manifest.PackageName, err.Error())) - continue - } - if c.IsDefaultChannel(manifest) { - hasDefault = true - if _, err := addDefaultChannel.Exec(c.Name, manifest.PackageName); err != nil { - errs = append(errs, fmt.Errorf("failed to add default channel %q in package %q: %s", c.Name, manifest.PackageName, err.Error())) - continue - } - } - channels = append(channels, c) - } - if !hasDefault { - errs = append(errs, fmt.Errorf("no default channel specified for %s", manifest.PackageName)) - } - - for _, c := range channels { - res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, startDepth) - if err != nil { - errs = append(errs, fmt.Errorf("failed to add channel %q in package %q: %s", c.Name, manifest.PackageName, err.Error())) - continue - } - currentID, err := res.LastInsertId() - if err != nil { - errs = append(errs, err) - continue - } - - channelEntryCSVName := c.CurrentCSVName - - // depth is set to `startDepth + 1` here because we already added the channel head - // with depth `startDepth` above. - depth := startDepth + 1 - - // Since this loop depends on following 'replaces', keep track of where it's been - replaceCycle := map[string]bool{channelEntryCSVName: true} - for { - // Get CSV for current entry - replaces, skips, version, err := s.getBundleSkipsReplacesVersion(tx, channelEntryCSVName) - if err != nil { - errs = append(errs, err) - break - } - - bundlePath, err := s.getBundlePathIfExists(tx, channelEntryCSVName) - if err != nil { - // this should only happen on an SQL error, bundlepath just not being set is for backwards compatibility reasons - errs = append(errs, err) - break - } - - if err := s.addPackageProperty(tx, channelEntryCSVName, manifest.PackageName, version, bundlePath); err != nil { - errs = append(errs, err) - break - } - - deprecated, err := s.deprecated(tx, channelEntryCSVName) - if err != nil { - errs = append(errs, err) - break - } - if deprecated { - // The package is truncated below this point, we're done! - break - } - - for _, skip := range skips { - // add dummy channel entry for the skipped version - skippedChannelEntry, err := addChannelEntry.Exec(c.Name, manifest.PackageName, skip, depth) - if err != nil { - errs = append(errs, fmt.Errorf("failed to add channel %q for skipped version %q in package %q: %s", c.Name, skip, manifest.PackageName, err.Error())) - continue - } - - skippedID, err := skippedChannelEntry.LastInsertId() - if err != nil { - errs = append(errs, err) - continue - } - - // add another channel entry for the parent, which replaces the skipped - synthesizedChannelEntry, err := addChannelEntry.Exec(c.Name, manifest.PackageName, channelEntryCSVName, depth) - if err != nil { - errs = append(errs, fmt.Errorf("failed to add channel %q for replaces %q in package %q: %s", c.Name, channelEntryCSVName, manifest.PackageName, err.Error())) - continue - } - - synthesizedID, err := synthesizedChannelEntry.LastInsertId() - if err != nil { - errs = append(errs, err) - continue - } - - if _, err = addReplaces.Exec(skippedID, synthesizedID); err != nil { - errs = append(errs, err) - continue - } - - depth++ - } - - // create real replacement chain - if replaces == "" { - // we've walked the channel until there was no replacement - break - } - - replacedChannelEntry, err := addChannelEntry.Exec(c.Name, manifest.PackageName, replaces, depth) - if err != nil { - errs = append(errs, fmt.Errorf("failed to add channel %q for replaces %q in package %q: %s", c.Name, replaces, manifest.PackageName, err.Error())) - break - } - - // If we find 'replaces' in the circuit list then we've seen it already, break out - if _, ok := replaceCycle[replaces]; ok { - //nolint:staticcheck // ST1005: error message is intentionally capitalized - errs = append(errs, fmt.Errorf("Cycle detected, %s replaces %s", channelEntryCSVName, replaces)) - break - } - replaceCycle[replaces] = true - - replacedID, err := replacedChannelEntry.LastInsertId() - if err != nil { - errs = append(errs, err) - break - } - if _, err = addReplaces.Exec(replacedID, currentID); err != nil { - errs = append(errs, err) - break - } - if _, _, _, err := s.getBundleSkipsReplacesVersion(tx, replaces); err != nil { - //nolint:staticcheck // ST1005: error message is intentionally capitalized - errs = append(errs, fmt.Errorf("Invalid bundle %s, replaces nonexistent bundle %s", c.CurrentCSVName, replaces)) - break - } - - currentID = replacedID - channelEntryCSVName = replaces - depth++ - } - } - return utilerrors.NewAggregate(errs) -} - -func (s *sqlLoader) ClearNonHeadBundles() error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - removeNonHeadBundles, err := tx.Prepare(` - update operatorbundle set bundle = null, csv = null - where (bundlepath != null or bundlepath != "") - and name not in ( - select operatorbundle.name from operatorbundle - join channel on channel.head_operatorbundle_name = operatorbundle.name - ) - `) - if err != nil { - return err - } - defer removeNonHeadBundles.Close() - - _, err = removeNonHeadBundles.Exec() - if err != nil { - return err - } - return tx.Commit() -} - -func (s *sqlLoader) getBundleSkipsReplacesVersion(tx *sql.Tx, bundleName string) (string, []string, string, error) { - getReplacesSkipsAndVersions, err := tx.Prepare(` - SELECT replaces, skips, version - FROM operatorbundle - WHERE operatorbundle.name=? LIMIT 1`) - if err != nil { - return "", nil, "", err - } - defer getReplacesSkipsAndVersions.Close() - - rows, rerr := getReplacesSkipsAndVersions.Query(bundleName) - if err != nil { - err = rerr - return "", nil, "", err - } - defer rows.Close() - if !rows.Next() { - err = fmt.Errorf("no bundle found for bundlename %s", bundleName) - return "", nil, "", err - } - - var replacesStringSQL sql.NullString - var skipsStringSQL sql.NullString - var versionStringSQL sql.NullString - if err = rows.Scan(&replacesStringSQL, &skipsStringSQL, &versionStringSQL); err != nil { - return "", nil, "", err - } - - var replaces string - var skips []string - var version string - if replacesStringSQL.Valid { - replaces = replacesStringSQL.String - } - if skipsStringSQL.Valid && len(skipsStringSQL.String) > 0 { - skips = strings.Split(skipsStringSQL.String, ",") - } - if versionStringSQL.Valid { - version = versionStringSQL.String - } - - return replaces, skips, version, nil -} - -func (s *sqlLoader) getBundlePathIfExists(tx *sql.Tx, bundleName string) (string, error) { - getBundlePath, err := tx.Prepare(` - SELECT bundlepath - FROM operatorbundle - WHERE operatorbundle.name=? LIMIT 1`) - if err != nil { - return "", err - } - defer getBundlePath.Close() - - rows, rerr := getBundlePath.Query(bundleName) - if err != nil { - err = rerr - return "", err - } - defer rows.Close() - if !rows.Next() { - // no bundlepath set - return "", nil - } - - var bundlePathSQL sql.NullString - if err = rows.Scan(&bundlePathSQL); err != nil { - return "", err - } - - var bundlePath string - if bundlePathSQL.Valid { - bundlePath = bundlePathSQL.String - } - - return bundlePath, nil -} - -func (s *sqlLoader) addAPIs(tx *sql.Tx, bundle *registry.Bundle) error { - if bundle.Name == "" { - return fmt.Errorf("cannot add apis for bundle with no name: %#v", bundle) - } - addAPI, err := tx.Prepare("insert or ignore into api(group_name, version, kind, plural) values(?, ?, ?, ?)") - if err != nil { - return err - } - defer addAPI.Close() - - addAPIProvider, err := tx.Prepare("insert into api_provider(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)") - if err != nil { - return err - } - defer addAPIProvider.Close() - - addAPIRequirer, err := tx.Prepare("insert into api_requirer(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)") - if err != nil { - return err - } - defer addAPIRequirer.Close() - - providedApis, err := bundle.ProvidedAPIs() - if err != nil { - return err - } - requiredApis, err := bundle.RequiredAPIs() - if err != nil { - return err - } - bundleVersion, err := bundle.Version() - if err != nil { - return err - } - - sqlString := func(s string) sql.NullString { - return sql.NullString{String: s, Valid: s != ""} - } - for api := range providedApis { - if _, err := addAPI.Exec(api.Group, api.Version, api.Kind, api.Plural); err != nil { - return err - } - - if _, err := addAPIProvider.Exec(api.Group, api.Version, api.Kind, bundle.Name, sqlString(bundleVersion), sqlString(bundle.BundleImage)); err != nil { - return err - } - } - for api := range requiredApis { - if _, err := addAPI.Exec(api.Group, api.Version, api.Kind, api.Plural); err != nil { - return err - } - - if _, err := addAPIRequirer.Exec(api.Group, api.Version, api.Kind, bundle.Name, sqlString(bundleVersion), sqlString(bundle.BundleImage)); err != nil { - return err - } - } - - return nil -} - -func (s *sqlLoader) getCSVNames(tx *sql.Tx, packageName string) ([]string, error) { - getID, err := tx.Prepare(` - SELECT DISTINCT channel_entry.operatorbundle_name - FROM channel_entry - WHERE channel_entry.package_name=?`) - - if err != nil { - return nil, err - } - defer getID.Close() - - rows, err := getID.Query(packageName) - if err != nil { - return nil, err - } - - var csvName string - csvNames := []string{} - for rows.Next() { - err := rows.Scan(&csvName) - if err != nil { - if nerr := rows.Close(); nerr != nil { - return nil, nerr - } - return nil, err - } - csvNames = append(csvNames, csvName) - } - - if err := rows.Close(); err != nil { - return nil, err - } - - return csvNames, nil -} - -func (s *sqlLoader) RemovePackage(packageName string) error { - if err := func() error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - csvNames, err := s.getCSVNames(tx, packageName) - if err != nil { - return err - } - if len(csvNames) == 0 { - return fmt.Errorf("no package found for packagename %s", packageName) - } - for _, csvName := range csvNames { - if err := s.rmBundle(tx, csvName); err != nil { - return err - } - } - - deletePackage, err := tx.Prepare("DELETE FROM package WHERE package.name=?") - if err != nil { - return err - } - defer deletePackage.Close() - - if _, err := deletePackage.Exec(packageName); err != nil { - return err - } - - deleteChannel, err := tx.Prepare("DELETE FROM channel WHERE package_name = ?") - if err != nil { - return err - } - defer deleteChannel.Close() - - if _, err := deleteChannel.Exec(packageName); err != nil { - return err - } - - return tx.Commit() - }(); err != nil { - return err - } - - // separate transaction so that we remove stranded bundles after the package has been cleared - return s.RemoveStrandedBundles() -} - -func (s *sqlLoader) rmBundle(tx *sql.Tx, csvName string) error { - deleteBundle, err := tx.Prepare("DELETE FROM operatorbundle WHERE operatorbundle.name=?") - if err != nil { - return err - } - defer deleteBundle.Close() - - if _, err := deleteBundle.Exec(csvName); err != nil { - return err - } - - deleteProvider, err := tx.Prepare("DELETE FROM api_provider WHERE api_provider.operatorbundle_name=?") - if err != nil { - return err - } - defer deleteProvider.Close() - - if _, err := deleteProvider.Exec(csvName); err != nil { - return err - } - - deleteRequirer, err := tx.Prepare("DELETE FROM api_requirer WHERE api_requirer.operatorbundle_name=?") - if err != nil { - return err - } - defer deleteRequirer.Close() - - if _, err := deleteRequirer.Exec(csvName); err != nil { - return err - } - - deleteChannelEntries, err := tx.Prepare("DELETE FROM channel_entry WHERE channel_entry.operatorbundle_name=?") - if err != nil { - return err - } - defer deleteChannelEntries.Close() - - if _, err := deleteChannelEntries.Exec(csvName); err != nil { - return err - } - - return nil -} - -func (s *sqlLoader) AddBundleSemver(graph *registry.Package, bundle *registry.Bundle) error { - err := s.AddOperatorBundle(bundle) - if err != nil { - return err - } - - err = s.AddPackageChannelsFromGraph(graph) - if err != nil { - return err - } - - return nil -} - -func (s *sqlLoader) AddBundlePackageChannels(manifest registry.PackageManifest, bundle *registry.Bundle) error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - if err := s.addOperatorBundle(tx, bundle); err != nil { - return err - } - - if err := s.rmPackage(tx, manifest.PackageName); err != nil { - return err - } - - if err := s.addPackageChannels(tx, manifest); err != nil { - return err - } - - return tx.Commit() -} - -func (s *sqlLoader) rmPackage(tx *sql.Tx, pkg string) error { - // Delete package, channel, and entries - they will be recalculated - deletePkg, err := tx.Prepare("DELETE FROM package WHERE name = ?") - if err != nil { - return err - } - - defer deletePkg.Close() - _, err = deletePkg.Exec(pkg) - if err != nil { - return fmt.Errorf("unable to delete the package %s: %s", pkg, err) - } - - deleteChan, err := tx.Prepare("DELETE FROM channel WHERE package_name = ?") - if err != nil { - return err - } - - defer deleteChan.Close() - _, err = deleteChan.Exec(pkg) - if err != nil { - return fmt.Errorf("unable to delete channel: %s", err) - } - - deleteChannelEntries, err := tx.Prepare("DELETE FROM channel_entry WHERE package_name = ?") - if err != nil { - return err - } - - defer deleteChannelEntries.Close() - _, err = deleteChannelEntries.Exec(pkg) - - return err -} - -func (s *sqlLoader) addDependencies(tx *sql.Tx, bundle *registry.Bundle) error { - addDep, err := tx.Prepare("insert into dependencies(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?)") - if err != nil { - return err - } - defer addDep.Close() - - bundleVersion, err := bundle.Version() - if err != nil { - return err - } - - sqlString := func(s string) sql.NullString { - return sql.NullString{String: s, Valid: s != ""} - } - for _, dep := range bundle.Dependencies { - if _, err := addDep.Exec(dep.Type, dep.Value, bundle.Name, sqlString(bundleVersion), sqlString(bundle.BundleImage)); err != nil { - return err - } - } - - // Look up requiredAPIs in CSV and add them in dependencies table - requiredApis, err := bundle.RequiredAPIs() - if err != nil { - return err - } - - for api := range requiredApis { - dep := registry.GVKDependency{ - Group: api.Group, - Kind: api.Kind, - Version: api.Version, - } - value, err := json.Marshal(dep) - if err != nil { - return err - } - if _, err := addDep.Exec(registry.GVKType, value, bundle.Name, sqlString(bundleVersion), sqlString(bundle.BundleImage)); err != nil { - return err - } - } - - return nil -} - -func (s *sqlLoader) addProperty(tx *sql.Tx, propType, value, bundleName, version, path string) error { - addProp, err := tx.Prepare("insert into properties(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?)") - if err != nil { - return err - } - defer addProp.Close() - - sqlString := func(s string) sql.NullString { - return sql.NullString{String: s, Valid: s != ""} - } - - if _, err := addProp.Exec(propType, value, bundleName, sqlString(version), sqlString(path)); err != nil { - return err - } - return nil -} - -func (s *sqlLoader) addPackageProperty(tx *sql.Tx, bundleName, pkg, version, bundlePath string) error { - // Add the package property - prop := registry.PackageProperty{ - PackageName: pkg, - Version: version, - } - value, err := json.Marshal(prop) - if err != nil { - return err - } - - return s.addProperty(tx, registry.PackageType, string(value), bundleName, version, bundlePath) -} - -func (s *sqlLoader) addBundleProperties(tx *sql.Tx, bundle *registry.Bundle) error { - type propstring struct { - Type string - Value string - } - properties := make(map[propstring]struct{}) - - bundleVersion, err := bundle.Version() - if err != nil { - return err - } - - for _, prop := range bundle.Properties { - value, err := json.Marshal(prop.Value) - if err != nil { - return err - } - properties[propstring{Type: prop.Type, Value: string(value)}] = struct{}{} - } - - // Look up providedAPIs in CSV and add them in properties table - providedApis, err := bundle.ProvidedAPIs() - if err != nil { - return err - } - - for api := range providedApis { - prop := registry.GVKProperty{ - Group: api.Group, - Kind: api.Kind, - Version: api.Version, - } - value, err := json.Marshal(prop) - if err != nil { - return err - } - properties[propstring{Type: registry.GVKType, Value: string(value)}] = struct{}{} - } - - // Add properties from annotations - csv, err := bundle.ClusterServiceVersion() - if err != nil { - // FIXME: Returning nil here is in line with the original implementation, but that was probably wrong. We should probably just bubble-up the error. - return nil - } - - if csv == nil { - // FIXME: Currently, a CSV is requirement of bundle addition. Should this return an error? - return nil - } - - var props []registry.Property - if csv.GetAnnotations() != nil { - v, ok := csv.GetAnnotations()[registry.PropertyKey] - if ok { - if err := json.Unmarshal([]byte(v), &props); err != nil { - return err - } - } - } - - for _, prop := range props { - value, err := json.Marshal(&prop.Value) - if err != nil { - return err - } - - // validate if Type is known - switch prop.Type { - case registry.LabelType: - if err := json.Unmarshal(prop.Value, ®istry.LabelProperty{}); err != nil { - return err - } - case registry.PackageType: - if err := json.Unmarshal(prop.Value, ®istry.PackageProperty{}); err != nil { - return err - } - case registry.GVKType: - if err := json.Unmarshal(prop.Value, ®istry.GVKProperty{}); err != nil { - return err - } - case registry.DeprecatedType: - // deprecated has no value - } - - properties[propstring{Type: prop.Type, Value: string(value)}] = struct{}{} - } - - // If the bundle has been deprecated before, readd the deprecated property - deprecated, err := s.deprecated(tx, bundle.Name) - if err != nil { - return err - } - if deprecated { - value, err := json.Marshal(registry.DeprecatedProperty{}) - if err != nil { - return err - } - properties[propstring{Type: registry.DeprecatedType, Value: string(value)}] = struct{}{} - } - - for prop := range properties { - if err := s.addProperty(tx, prop.Type, prop.Value, bundle.Name, bundleVersion, bundle.BundleImage); err != nil { - return err - } - } - - return nil -} - -func (s *sqlLoader) rmSharedChannelEntry(tx *sql.Tx, csvName, unsharedCsv string) error { - if len(unsharedCsv) == 0 { - return nil - } - - // remove any edges that replace bundle to be removed on the channels of the unsharedCsv - _, err := tx.Exec(` - UPDATE channel_entry - SET replaces=NULL - WHERE replaces IN ( - SELECT entry_id FROM channel_entry - WHERE operatorbundle_name = ? - AND channel_name IN ( - SELECT channel_name FROM channel_entry - WHERE operatorbundle_name = ? - ))`, csvName, unsharedCsv) - if err != nil { - return err - } - - // delete the channel entries on the shared channel list - _, err = tx.Exec(` - DELETE FROM channel_entry - WHERE operatorbundle_name = ? - AND channel_name IN ( - SELECT channel_name - FROM channel_entry - WHERE operatorbundle_name = ? - )`, csvName, unsharedCsv) - if err != nil { - return err - } - - return nil -} - -type tailBundle struct { - name string - version string - bundlepath string - channels []string - replaces []string // in addition to the replaces chain, there may also be real skipped entries - replacedBy []string // to handle any chain where a skipped entry may be a part of another channel that should not be truncated -} - -func getTailFromBundle(tx *sql.Tx, head string) (map[string]tailBundle, error) { - // traverse replaces chain and collect channel list for each bundle. - // This assumes that replaces chain for a bundle is the same across channels. - // only real bundles with entries in the operator_bundle table are returned. - getReplacesChain := ` - WITH RECURSIVE - replaces_entry (operatorbundle_name, replaces, replaced_by, channel_name, package_name) AS ( - SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name - FROM channel_entry - LEFT OUTER JOIN channel_entry AS replaces - ON replaces.entry_id = channel_entry.replaces - LEFT OUTER JOIN channel_entry AS replaced_by - ON channel_entry.entry_id = replaced_by.replaces - WHERE channel_entry.operatorbundle_name = ? - UNION - SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name - FROM channel_entry - JOIN replaces_entry - ON replaces_entry.replaces = channel_entry.operatorbundle_name - LEFT OUTER JOIN channel_entry AS replaces - ON channel_entry.replaces = replaces.entry_id - LEFT OUTER JOIN channel_entry AS replaced_by - ON channel_entry.entry_id = replaced_by.replaces - ) - SELECT - replaces_entry.operatorbundle_name, - operatorbundle.version, - operatorbundle.bundlepath, - GROUP_CONCAT(DISTINCT replaces_entry.channel_name), - GROUP_CONCAT(DISTINCT replaces_entry.replaces), - GROUP_CONCAT(DISTINCT replaces_entry.replaced_by) - FROM replaces_entry - LEFT OUTER JOIN operatorbundle - ON operatorbundle.name = replaces_entry.operatorbundle_name - GROUP BY replaces_entry.operatorbundle_name, replaces_entry.package_name - ORDER BY replaces_entry.channel_name, replaces_entry.replaces, replaces_entry.replaced_by` - - getDefaultChannelHead := ` - SELECT head_operatorbundle_name FROM channel - INNER JOIN package ON channel.name = package.default_channel AND channel.package_name = package.name - INNER JOIN channel_entry on channel.package_name = channel_entry.package_name - WHERE channel_entry.operatorbundle_name = ? - LIMIT 1` - - row := tx.QueryRow(getDefaultChannelHead, head) - if row == nil { - return nil, fmt.Errorf("could not find default channel head for %s", head) - } - var defaultChannelHead sql.NullString - err := row.Scan(&defaultChannelHead) - if err != nil { - return nil, fmt.Errorf("error getting default channel head for %s: %v", head, err) - } - if !defaultChannelHead.Valid || len(defaultChannelHead.String) == 0 { - return nil, fmt.Errorf("invalid default channel head '%s' for %s", defaultChannelHead.String, head) - } - - rows, err := tx.QueryContext(context.TODO(), getReplacesChain, head) - if err != nil { - return nil, err - } - replacesChain := map[string]tailBundle{} - for rows.Next() { - var ( - bundle sql.NullString - version sql.NullString - bundlepath sql.NullString - channels sql.NullString - replaces sql.NullString - replacedBy sql.NullString - ) - if err := rows.Scan(&bundle, &version, &bundlepath, &channels, &replaces, &replacedBy); err != nil { - if nerr := rows.Close(); nerr != nil { - return nil, nerr - } - return nil, err - } - if !bundle.Valid || len(bundle.String) == 0 { - return nil, fmt.Errorf("invalid tail bundle %v for %s", bundle, head) - } - - if bundle.String == defaultChannelHead.String { - // A nil error indicates that next is the default channel head - return nil, registry.ErrRemovingDefaultChannelDuringDeprecation - } - var channelList, replacesList, replacedList []string - if channels.Valid && len(channels.String) > 0 { - channelList = strings.Split(channels.String, ",") - } - if replaces.Valid && len(replaces.String) > 0 { - replacesList = strings.Split(replaces.String, ",") - } - if replacedBy.Valid && len(replacedBy.String) > 0 { - replacedList = strings.Split(replacedBy.String, ",") - } - - replacesChain[bundle.String] = tailBundle{ - name: bundle.String, - version: version.String, - bundlepath: bundlepath.String, - channels: channelList, - replaces: replacesList, - replacedBy: replacedList, - } - } - if err := rows.Close(); err != nil { - return nil, err - } - return replacesChain, nil -} - -func getBundleNameAndVersionForImage(tx *sql.Tx, path string) (string, string, error) { - query := `SELECT name, version FROM operatorbundle WHERE bundlepath=? LIMIT 1` - rows, err := tx.QueryContext(context.TODO(), query, path) - if err != nil { - return "", "", err - } - defer rows.Close() - - var name sql.NullString - var version sql.NullString - if rows.Next() { - if err := rows.Scan(&name, &version); err != nil { - return "", "", err - } - } - if name.Valid && version.Valid { - return name.String, version.String, nil - } - return "", "", registry.ErrBundleImageNotInDatabase -} - -func (s *sqlLoader) DeprecateBundle(path string) error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - name, version, err := getBundleNameAndVersionForImage(tx, path) - if err != nil { - return err - } - tailBundles, err := getTailFromBundle(tx, name) - if err != nil { - return err - } - - // track bundles that have already been added to removeOrDeprecate - removeOrDeprecate := []string{name} - seen := map[string]bool{name: true} - - headChannelsMap := map[string]struct{}{} - if _, ok := tailBundles[name]; ok { - for _, c := range tailBundles[name].channels { - headChannelsMap[c] = struct{}{} - } - } - - // Traverse replaces chain, removing bundles from all channels the initial deprecated bundle belongs to. - // If a bundle is removed from all its channels, it is truncated. -deprecate: - for ; len(removeOrDeprecate) > 0; removeOrDeprecate = removeOrDeprecate[1:] { - bundle := removeOrDeprecate[0] - if _, ok := tailBundles[bundle]; !ok { - continue - } - for _, b := range tailBundles[bundle].replaces { - if !seen[b] { - removeOrDeprecate = append(removeOrDeprecate, b) - seen[b] = true - } - } - if bundle == name { - // head bundle gets deprecated separately - continue - } - - // remove all channel_entries for bundle with same channel as the deprecated one - if err := s.rmSharedChannelEntry(tx, bundle, name); err != nil { - return err - } - - //rm channel entries for channels - if len(tailBundles[bundle].channels) > len(headChannelsMap) { - // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this - continue - } - for _, c := range tailBundles[bundle].channels { - if _, ok := headChannelsMap[c]; !ok { - // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this - continue deprecate - } - } - for _, b := range tailBundles[bundle].replacedBy { - if _, ok := tailBundles[b]; !ok { - // bundle is a replaces edge for some csv that isn't in the deprecated tail, can't be replaced safely. - continue deprecate - } - } - - // Remove bundle - if err := s.rmBundle(tx, bundle); err != nil { - return err - } - } - // remove links to deprecated/truncated bundles to avoid regenerating these on add/overwrite - _, err = tx.Exec(`UPDATE channel_entry SET replaces=NULL WHERE operatorbundle_name=?`, name) - if err != nil { - return err - } - - // a channel with a deprecated head is still visible on the console unless the channel_entry table has no entries for it - _, err = tx.Exec(`DELETE FROM channel WHERE head_operatorbundle_name=?`, name) - if err != nil { - return err - } - - deprecatedValue, err := json.Marshal(registry.DeprecatedProperty{}) - if err != nil { - return err - } - err = s.addProperty(tx, registry.DeprecatedType, string(deprecatedValue), name, version, path) - if err != nil { - return err - } - - // Create a persistent record of the bundle's deprecation - // This lets us recover from losing the properties and augmented bundle rows - _, err = tx.Exec("INSERT OR REPLACE INTO deprecated(operatorbundle_name) VALUES(?)", name) - if err != nil { - return err - } - - if err := s.rmStrandedDeprecated(tx); err != nil { - return err - } - return tx.Commit() -} - -func (s *sqlLoader) RemoveStrandedBundles() error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - if err := s.rmStrandedBundles(tx); err != nil { - return err - } - - if err := s.rmStrandedDeprecated(tx); err != nil { - return err - } - return tx.Commit() -} - -func (s *sqlLoader) rmStrandedBundles(tx *sql.Tx) error { - // Remove everything without a channel_entry except deprecated channel heads - _, err := tx.Exec("DELETE FROM operatorbundle WHERE name NOT IN(select operatorbundle_name from channel_entry) AND name NOT IN (SELECT operatorbundle_name FROM deprecated)") - return err -} - -func (s *sqlLoader) rmStrandedDeprecated(tx *sql.Tx) error { - // Remove any deprecated channel heads which have no entries in the channel/channel_entry table to avoid being displayed on the console - rows, err := tx.Query("SELECT DISTINCT name FROM package") - if err != nil { - return err - } - defer rows.Close() - knownPackages := map[string]struct{}{} - for rows.Next() { - var pkg sql.NullString - if err := rows.Scan(&pkg); err != nil { - return err - } - if !pkg.Valid || len(pkg.String) == 0 { - return fmt.Errorf("invalid package %v", pkg) - } - knownPackages[pkg.String] = struct{}{} - } - - packagePropertiesQuery := `select distinct operatorbundle_name, value from properties where type = ?` - pRows, err := tx.Query(packagePropertiesQuery, registry.PackageType) - if err != nil { - return err - } - defer pRows.Close() - - for pRows.Next() { - var bundle, value sql.NullString - if err := pRows.Scan(&bundle, &value); err != nil { - return err - } - - if !bundle.Valid || len(bundle.String) == 0 { - return fmt.Errorf("invalid bundle %v", bundle) - } - - if !value.Valid || len(value.String) == 0 { - return fmt.Errorf("invalid package property on %v: %v", bundle, value) - } - - var prop registry.PackageProperty - if err := json.Unmarshal([]byte(value.String), &prop); err != nil { - return err - } - - if _, ok := knownPackages[prop.PackageName]; !ok { - if err := s.rmBundle(tx, bundle.String); err != nil { - return err - } - } - } - - // Clean up the deprecated table by dropping all truncated bundles - // (see pkg/sqlite/migrations/013_rm_truncated_deprecations.go for more details) - _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT name FROM operatorbundle)`) - return err -} - -func (s *sqlLoader) getBundlesThatSubstitutesFor(tx *sql.Tx, replaces string) ([]string, error) { - query := `SELECT name FROM operatorbundle WHERE substitutesfor=?` - rows, err := tx.QueryContext(context.TODO(), query, replaces) - if err != nil { - return []string{}, err - } - defer rows.Close() - - var substitutesFor []string - var subsFor sql.NullString - for rows.Next() { - if err := rows.Scan(&subsFor); err != nil { - return []string{}, err - } - if subsFor.Valid && subsFor.String != "" { - substitutesFor = append(substitutesFor, subsFor.String) - } - } - return substitutesFor, nil -} - -func (s *sqlLoader) getBundleSubstitution(tx *sql.Tx, name string) (string, error) { - query := `SELECT substitutesfor FROM operatorbundle WHERE name=?` - rows, err := tx.QueryContext(context.TODO(), query, name) - if err != nil { - return "", err - } - defer rows.Close() - - var substitutesFor sql.NullString - if rows.Next() { - if err := rows.Scan(&substitutesFor); err != nil { - return "", err - } - } - return substitutesFor.String, nil -} - -func (s *sqlLoader) deprecated(tx *sql.Tx, name string) (bool, error) { - var err error - if row := tx.QueryRow(`SELECT * FROM deprecated WHERE operatorbundle_name = ?`, name); row != nil { - err = row.Scan(&sql.NullString{}) - } - if err == sql.ErrNoRows { - return false, nil - } - - // Ignore any deprecated bundles - return err == nil, err -} - -// DeprecationAwareLoader understands how bundle deprecations are handled in SQLite and decorates -// the sqlLoader with proxy methods that handle deprecation related table housekeeping. -type DeprecationAwareLoader struct { - *sqlLoader -} - -// NewDeprecationAwareLoader returns a new DeprecationAwareLoader. -func NewDeprecationAwareLoader(db *sql.DB, opts ...DbOption) (*DeprecationAwareLoader, error) { - loader, err := newSQLLoader(db, opts...) - if err != nil { - return nil, err - } - - return &DeprecationAwareLoader{sqlLoader: loader}, nil -} - -func (d *DeprecationAwareLoader) clearLastDeprecatedInPackage(pkg string) error { - tx, err := d.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - // The last deprecated bundles for a package will still have "tombstone" records in channel_entry (among other tables). - // Use that info to relate the package to a set of rows in the deprecated table. - _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN channel_entry ON deprecated.operatorbundle_name = channel_entry.operatorbundle_name) WHERE channel_entry.package_name = ?)`, pkg) - if err != nil { - return err - } - - return tx.Commit() -} - -func (d *DeprecationAwareLoader) RemovePackage(pkg string) error { - if err := d.clearLastDeprecatedInPackage(pkg); err != nil { - return err - } - - return d.sqlLoader.RemovePackage(pkg) -} - -// RemoveOverwrittenChannelHead removes a bundle if it is the channel head and has nothing replacing it -func (s sqlLoader) RemoveOverwrittenChannelHead(pkg, bundle string) error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - // check if bundle has anything that replaces it - getBundlesThatReplaceHeadQuery := `SELECT DISTINCT operatorbundle.name AS replaces, channel_entry.channel_name - FROM channel_entry - LEFT OUTER JOIN channel_entry replaces - ON replaces.replaces = channel_entry.entry_id - INNER JOIN operatorbundle - ON replaces.operatorbundle_name = operatorbundle.name - WHERE channel_entry.package_name = ? - AND channel_entry.operatorbundle_name = ? - LIMIT 1` - - rows, err := tx.QueryContext(context.TODO(), getBundlesThatReplaceHeadQuery, pkg, bundle) - if err != nil { - return err - } - defer rows.Close() - if rows != nil { - for rows.Next() { - var replaces, channel sql.NullString - if err := rows.Scan(&replaces, &channel); err != nil { - return err - } - // This is not a head bundle for all channels it is a member of. Cannot remove - // nolint: staticcheck - return fmt.Errorf("cannot overwrite bundle %s from package %s: replaced by %s on channel %s", bundle, pkg, replaces.String, channel.String) - } - } - - getReplacingBundlesQuery := ` - SELECT replaces.name as replaces, channel_entry.channel_name, min(depth) - from channel_entry - LEFT JOIN ( - SELECT entry_id, name FROM channel_entry - INNER JOIN operatorbundle - ON channel_entry.operatorbundle_name = operatorbundle.name - ) AS replaces - ON channel_entry.replaces = replaces.entry_id - WHERE channel_entry.package_name = ? - AND channel_entry.operatorbundle_name = ? - GROUP BY channel_name - ` - - pRows, err := tx.QueryContext(context.TODO(), getReplacingBundlesQuery, pkg, bundle) - if err != nil { - return err - } - defer pRows.Close() - - channelHeadUpdateQuery := `UPDATE channel SET head_operatorbundle_name = ? WHERE package_name = ? AND name = ? AND head_operatorbundle_name = ?` - for pRows.Next() { - var replaces, channel sql.NullString - var depth sql.NullInt64 - if err := pRows.Scan(&replaces, &channel, &depth); err != nil { - return err - } - - if !channel.Valid { - return fmt.Errorf("channel name column corrupt for bundle %s", bundle) - } - if replaces.Valid && len(replaces.String) != 0 { - // replace any valid entries as channel heads to avoid rmBundle from truncating the entire channel - if _, err = tx.Exec(channelHeadUpdateQuery, replaces, pkg, channel, bundle); err != nil { - return err - } - } else { - // NULL default channel before dropping to let packagemanifest detect default channel - if _, err := tx.Exec(`UPDATE channel SET head_operatorbundle_name = NULL WHERE name = ? AND package_name = ? AND name IN (SELECT default_channel FROM package WHERE name = ?)`, channel, pkg, pkg); err != nil { - return err - } - } - } - - if err := s.rmBundle(tx, bundle); err != nil { - return err - } - // remove from deprecated - if _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name = ?`, bundle); err != nil { - return err - } - err = tx.Commit() - if err != nil { - return err - } - return nil -} diff --git a/pkg/sqlite/load_test.go b/pkg/sqlite/load_test.go deleted file mode 100644 index 1c31090c2..000000000 --- a/pkg/sqlite/load_test.go +++ /dev/null @@ -1,1278 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func TestAddPackageChannels(t *testing.T) { - type fields struct { - bundles []*registry.Bundle - } - type args struct { - pkgs []registry.PackageManifest - } - type expected struct { - errs []error - pkgs []string - } - tests := []struct { - description string - fields fields - args args - expected expected - }{ - { - description: "DuplicateBundlesInPackage/DBDoesntLock", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", "")), - newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", "")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-b", "")), - newBundle(t, "csv-c", "pkg-1", []string{"stable"}, newUnstructuredCSV(t, "csv-c", "")), - }, - }, - args: args{ - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-a", - }, - { - Name: "alpha", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "stable", - }, - { - PackageName: "pkg-1", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-c", - }, - }, - }, - }, - }, - expected: expected{ - errs: make([]error, 2), - pkgs: []string{ - "pkg-0", - "pkg-1", - }, - }, - }, - { - description: "MissingReplacesInPackage/AggregatesAndContinues", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", "csv-d")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-b", "")), - newBundle(t, "csv-c", "pkg-1", []string{"stable"}, newUnstructuredCSV(t, "csv-c", "")), - }, - }, - args: args{ - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-a", - }, - { - Name: "alpha", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "stable", - }, - { - PackageName: "pkg-1", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-c", - }, - }, - }, - }, - }, - expected: expected{ - errs: []error{ - utilerrors.NewAggregate([]error{fmt.Errorf("Invalid bundle csv-a, replaces nonexistent bundle csv-d")}), - nil, - }, - pkgs: []string{ - "pkg-1", - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - err = store.Migrate(context.TODO()) - require.NoError(t, err) - - for _, bundle := range tt.fields.bundles { - // Throw away any errors loading bundles (not testing this) - _ = store.AddOperatorBundle(bundle) - } - - for i, pkg := range tt.args.pkgs { - errs := store.AddPackageChannels(pkg) - require.Equal(t, tt.expected.errs[i], errs, "expected %v, got %v", tt.expected.errs[i], errs) - } - - // Ensure expected packages were loaded - querier := NewSQLLiteQuerierFromDb(db) - pkgs, err := querier.ListPackages(context.Background()) - require.NoError(t, err) - require.ElementsMatch(t, tt.expected.pkgs, pkgs) - }) - } -} - -func TestAddBundleSemver(t *testing.T) { - // Create a test DB - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - err = store.Migrate(context.TODO()) - require.NoError(t, err) - graphLoader, err := NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - // Seed the db with a replaces-mode bundle/package - replacesBundle := newBundle(t, "csv-a", "pkg-foo", []string{"stable"}, newUnstructuredCSV(t, "csv-a", "")) - err = store.AddOperatorBundle(replacesBundle) - require.NoError(t, err) - - err = store.AddPackageChannels(registry.PackageManifest{ - PackageName: "pkg-foo", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-a", - }, - }, - DefaultChannelName: "stable", - }) - require.NoError(t, err) - - // Add semver bundles in non-semver order. - bundles := []*registry.Bundle{ - newBundle(t, "csv-3", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-3", "0.3.0")), - newBundle(t, "csv-1", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-1", "0.1.0")), - newBundle(t, "csv-2", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-2", "0.2.0")), - } - for _, b := range bundles { - graph, err := graphLoader.Generate(b.Package) - require.Conditionf(t, func() bool { - return err == nil || errors.Is(err, registry.ErrPackageNotInDatabase) - }, "got unexpected error: %v", err) - bundleLoader := registry.BundleGraphLoader{} - updatedGraph, err := bundleLoader.AddBundleToGraph(b, graph, ®istry.AnnotationsFile{Annotations: *b.Annotations}, false) - require.NoError(t, err) - err = store.AddBundleSemver(updatedGraph, b) - require.NoError(t, err) - } - - // Ensure bundles can be queried with expected replaces and skips values. - querier := NewSQLLiteQuerierFromDb(db) - gotBundles, err := querier.ListBundles(context.Background()) - require.NoError(t, err) - replaces := map[string]string{} - for _, b := range gotBundles { - if b.PackageName != "pkg-0" { - continue - } - require.Empty(t, b.Skips, "unexpected skips value(s) for bundle %q", b.CsvName) - replaces[b.CsvName] = b.Replaces - } - require.Equal(t, map[string]string{ - "csv-3": "csv-2", - "csv-2": "csv-1", - "csv-1": "", - }, replaces) -} - -func TestClearNonHeadBundles(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - err = store.Migrate(context.TODO()) - require.NoError(t, err) - - // Create a replaces chain that contains bundles with no bundle path - pkg, channel := "pkg", "stable" - channels := []string{"stable"} - withoutPath := newBundle(t, "without-path", pkg, channels, newUnstructuredCSV(t, "without-path", "")) - withPathInternal := newBundle(t, "with-path-internal", pkg, channels, newUnstructuredCSV(t, "with-path-internal", withoutPath.Name)) - withPathInternal.BundleImage = "this.is/agood@sha256:path" - withPath := newBundle(t, "with-path", pkg, channels, newUnstructuredCSV(t, "with-path", withPathInternal.Name)) - withPath.BundleImage = "this.is/abetter@sha256:path" - - require.NoError(t, store.AddOperatorBundle(withoutPath)) - require.NoError(t, store.AddOperatorBundle(withPathInternal)) - require.NoError(t, store.AddOperatorBundle(withPath)) - err = store.AddPackageChannels(registry.PackageManifest{ - PackageName: pkg, - Channels: []registry.PackageChannel{ - { - Name: channel, - CurrentCSVName: withPath.Name, - }, - }, - DefaultChannelName: channel, - }) - require.NoError(t, err) - - // Clear everything but the default bundle - require.NoError(t, store.ClearNonHeadBundles()) - - // Internal node without bundle path should keep its manifests - querier := NewSQLLiteQuerierFromDb(db) - bundle, err := querier.GetBundle(context.Background(), pkg, channel, withoutPath.Name) - require.NoError(t, err) - require.NotNil(t, bundle) - require.NotNil(t, bundle.Object) - require.NotEmpty(t, bundle.CsvJson) - - // Internal node with bundle path should be cleared - bundle, err = querier.GetBundle(context.Background(), pkg, channel, withPathInternal.Name) - require.NoError(t, err) - require.NotNil(t, bundle) - require.Nil(t, bundle.Object) - require.Empty(t, bundle.CsvJson) - - // Head of the default channel should keep its manifests - bundle, err = querier.GetBundle(context.Background(), pkg, channel, withPath.Name) - require.NoError(t, err) - require.NotNil(t, bundle) - require.NotNil(t, bundle.Object) - require.NotEmpty(t, bundle.CsvJson) -} - -func newUnstructuredCSV(t *testing.T, name, replaces string) *unstructured.Unstructured { - csv := ®istry.ClusterServiceVersion{} - csv.Kind = "ClusterServiceVersion" - csv.SetName(name) - csv.Spec = json.RawMessage(fmt.Sprintf(`{"replaces": "%s"}`, replaces)) - - out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) - require.NoError(t, err) - return &unstructured.Unstructured{Object: out} -} - -func newUnstructuredCSVWithSkips(t *testing.T, name, replaces string, skips ...string) *unstructured.Unstructured { - csv := ®istry.ClusterServiceVersion{} - csv.Kind = "ClusterServiceVersion" - csv.SetName(name) - allSkips, err := json.Marshal(skips) - require.NoError(t, err) - replacesSkips := fmt.Sprintf(`{"replaces": "%s", "skips": %s}`, replaces, string(allSkips)) - csv.Spec = json.RawMessage(replacesSkips) - - out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) - require.NoError(t, err) - return &unstructured.Unstructured{Object: out} -} - -func newUnstructuredCSVWithVersion(t *testing.T, name, version string) *unstructured.Unstructured { - csv := ®istry.ClusterServiceVersion{} - csv.Kind = "ClusterServiceVersion" - csv.SetName(name) - versionJSON := fmt.Sprintf(`{"version": "%s"}`, version) - csv.Spec = json.RawMessage(versionJSON) - - out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) - require.NoError(t, err) - return &unstructured.Unstructured{Object: out} -} - -func newBundle(t *testing.T, name, pkgName string, channels []string, objs ...*unstructured.Unstructured) *registry.Bundle { - bundle := registry.NewBundle(name, ®istry.Annotations{ - PackageName: pkgName, - Channels: strings.Join(channels, ","), - }, objs...) - - // Bust the bundle cache to set the CSV and CRDs - _, err := bundle.ClusterServiceVersion() - require.NoError(t, err) - - return bundle -} - -func TestRMBundle(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.Background())) - tx, err := db.Begin() - require.NoError(t, err) - loader := store.(*sqlLoader) - require.NoError(t, loader.rmBundle(tx, "non-existent")) -} - -func TestDeprecationAwareLoader(t *testing.T) { - withBundleImage := func(image string, bundle *registry.Bundle) *registry.Bundle { - bundle.BundleImage = image - return bundle - } - type fields struct { - bundles []*registry.Bundle - pkgs []registry.PackageManifest - deprecatedPaths []string - } - type args struct { - pkg string - } - type expected struct { - err error - deprecated map[string]struct{} - nontruncated map[string]struct{} - } - tests := []struct { - description string - fields fields - args args - expected expected - }{ - { - description: "NoDeprecation", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", ""))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-a", - }, - }, - DefaultChannelName: "stable", - }, - }, - deprecatedPaths: []string{}, - }, - args: args{ - pkg: "pkg-0", - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{}, - nontruncated: map[string]struct{}{}, - }, - }, - { - description: "RemovePackage/DropsDeprecated", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", ""))), - withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-aa", - }, - }, - DefaultChannelName: "stable", - }, - }, - deprecatedPaths: []string{ - "quay.io/my/bundle-a", - }, - }, - args: args{ - pkg: "pkg-0", - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{}, - nontruncated: map[string]struct{}{}, - }, - }, - { - description: "RemovePackage/IgnoresOtherPackages", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", ""))), - withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), - withBundleImage("quay.io/my/bundle-b", newBundle(t, "csv-b", "pkg-1", []string{"stable"}, newUnstructuredCSV(t, "csv-b", ""))), - withBundleImage("quay.io/my/bundle-bb", newBundle(t, "csv-bb", "pkg-1", []string{"stable"}, newUnstructuredCSV(t, "csv-bb", "csv-b"))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-aa", - }, - }, - DefaultChannelName: "stable", - }, - { - PackageName: "pkg-1", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-bb", - }, - }, - DefaultChannelName: "stable", - }, - }, - deprecatedPaths: []string{ - "quay.io/my/bundle-a", - "quay.io/my/bundle-b", - }, - }, - args: args{ - pkg: "pkg-0", // Should result in a alone being dropped from the deprecated table - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{ - "csv-b": {}, - }, - nontruncated: map[string]struct{}{ - "csv-b:stable": {}, - "csv-bb:stable": {}, - }, - }, - }, - { - description: "DeprecateTruncate/DropsTruncated", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-a", ""))), - withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), - withBundleImage("quay.io/my/bundle-aaa", newBundle(t, "csv-aaa", "pkg-0", []string{"stable"}, newUnstructuredCSV(t, "csv-aaa", "csv-aa"))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "stable", - CurrentCSVName: "csv-aaa", - }, - }, - DefaultChannelName: "stable", - }, - }, - deprecatedPaths: []string{ - "quay.io/my/bundle-a", - "quay.io/my/bundle-aa", // Should truncate a, dropping it from the deprecated table - }, - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{ - "csv-aa": {}, // csv-b remains in the deprecated table since it has been truncated and hasn't been removed - }, - nontruncated: map[string]struct{}{ - "csv-aa:stable": {}, - "csv-aaa:stable": {}, - }, - }, - }, - { - description: "DeprecateTruncateRemoveDeprecatedChannelHeadOnPackageRemoval", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", ""))), - withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), - withBundleImage("quay.io/my/bundle-b", newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "csv-a"))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-aa", - }, - { - Name: "b", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "b", - }, - }, - deprecatedPaths: []string{ - "quay.io/my/bundle-aa", - }, - }, - args: args{ - pkg: "pkg-0", - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{}, - }, - }, - { - description: "DeprecateChannelHead", - fields: fields{ - bundles: []*registry.Bundle{ - withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", ""))), - withBundleImage("quay.io/my/bundle-b", newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "csv-a"))), - withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), - withBundleImage("quay.io/my/bundle-aaa", newBundle(t, "csv-aaa", "pkg-0", []string{"a"}, newUnstructuredCSVWithSkips(t, "csv-aaa", "csv-aa", "csv-cc"))), - withBundleImage("quay.io/my/bundle-cc", newBundle(t, "csv-cc", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-cc", "csv-c"))), - withBundleImage("quay.io/my/bundle-c", newBundle(t, "csv-c", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-c", ""))), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-aaa", - }, - { - Name: "b", - CurrentCSVName: "csv-b", - }, - { - Name: "c", - CurrentCSVName: "csv-cc", - }, - }, - DefaultChannelName: "b", - }, - }, - deprecatedPaths: []string{ - "quay.io/my/bundle-aaa", - }, - }, - expected: expected{ - err: nil, - deprecated: map[string]struct{}{ - "csv-aaa": {}, - }, - nontruncated: map[string]struct{}{ - "csv-a:b": {}, - "csv-aaa:": {}, - "csv-b:b": {}, - "csv-c:c": {}, - "csv-cc:c": {}, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewDeprecationAwareLoader(db) - require.NoError(t, err) - err = store.Migrate(context.TODO()) - require.NoError(t, err) - - for _, bundle := range tt.fields.bundles { - require.NoError(t, store.AddOperatorBundle(bundle)) - } - - for _, pkg := range tt.fields.pkgs { - require.NoError(t, store.AddPackageChannels(pkg)) - } - for _, deprecatedPath := range tt.fields.deprecatedPaths { - require.NoError(t, store.DeprecateBundle(deprecatedPath)) - } - - if tt.args.pkg != "" { - err = store.RemovePackage(tt.args.pkg) - if tt.expected.err != nil { - require.Error(t, err) - } else { - require.NoError(t, err) - } - } - - tx, err := db.Begin() - require.NoError(t, err) - - checkForBundles := func(query, table string, bundleMap map[string]struct{}) { - rows, err := tx.Query(query) - require.NoError(t, err) - require.NotNil(t, rows) - - var bundleName string - for rows.Next() { - require.NoError(t, rows.Scan(&bundleName)) - _, ok := bundleMap[bundleName] - require.True(t, ok, "bundle shouldn't be in the %s table: %s", table, bundleName) - delete(bundleMap, bundleName) - } - - require.Empty(t, bundleMap, "not all expected bundles exist in %s table: %v", table, bundleMap) - } - checkForBundles(`SELECT operatorbundle_name FROM deprecated`, "deprecated", tt.expected.deprecated) - // operatorbundle_name: - checkForBundles(`SELECT name||":"|| coalesce(group_concat(distinct channel_name), "") FROM (SELECT name, channel_name from operatorbundle left outer join channel_entry on name=operatorbundle_name order by channel_name) group by name`, "operatorbundle", tt.expected.nontruncated) - }) - } -} - -func TestGetTailFromBundle(t *testing.T) { - type fields struct { - bundles []*registry.Bundle - pkgs []registry.PackageManifest - } - type args struct { - bundle string - } - type expected struct { - err error - tail map[string]tailBundle - } - tests := []struct { - description string - fields fields - args args - expected expected - }{ - { - description: "ContainsDefaultChannel", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-a", "csv-b")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-b", "csv-c")), - newBundle(t, "csv-c", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-c", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "csv-a", - }, - { - Name: "stable", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "stable", - }, - }, - }, - args: args{ - bundle: "csv-a", - }, - expected: expected{ - err: registry.ErrRemovingDefaultChannelDuringDeprecation, - tail: nil, - }, - }, - { - description: "ContainsNoDefaultChannel", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-a", "csv-b")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-b", "csv-c")), - newBundle(t, "csv-c", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-c", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "csv-a", - }, - { - Name: "stable", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "alpha", - }, - }, - }, - args: args{ - bundle: "csv-b", - }, - expected: expected{ - err: nil, - tail: map[string]tailBundle{ - "csv-b": {name: "csv-b", channels: []string{"alpha", "stable"}, replaces: []string{"csv-c"}, replacedBy: []string{"csv-a"}}, - "csv-c": {name: "csv-c", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, - }, - }, - }, - { - description: "ContainsSkips", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-a", "csv-b")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha"}, newUnstructuredCSVWithSkips(t, "csv-b", "csv-c", "csv-d", "csv-e", "csv-f")), - newBundle(t, "csv-c", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-c", "csv-d")), - newBundle(t, "csv-d", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-d", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "csv-a", - }, - { - Name: "stable", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "alpha", - }, - }, - }, - args: args{ - bundle: "csv-b", - }, - expected: expected{ - err: nil, - tail: map[string]tailBundle{ - "csv-b": {name: "csv-b", channels: []string{"alpha", "stable"}, replaces: []string{"csv-c", "csv-d", "csv-e", "csv-f"}, replacedBy: []string{"csv-a"}}, - "csv-c": {name: "csv-c", channels: []string{"alpha", "stable"}, replaces: []string{"csv-d"}, replacedBy: []string{"csv-b"}}, - "csv-d": {name: "csv-d", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b", "csv-c"}}, - "csv-e": {name: "csv-e", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, - "csv-f": {name: "csv-f", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, - }, - }, - }, - { - description: "ContainsDefaultChannelFromSkips", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"alpha"}, newUnstructuredCSV(t, "csv-a", "csv-b")), - newBundle(t, "csv-b", "pkg-0", []string{"alpha"}, newUnstructuredCSVWithSkips(t, "csv-b", "csv-d", "csv-c")), - newBundle(t, "csv-c", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-c", "csv-d")), - newBundle(t, "csv-d", "pkg-0", []string{"alpha", "stable"}, newUnstructuredCSV(t, "csv-d", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "alpha", - CurrentCSVName: "csv-a", - }, - { - Name: "stable", - CurrentCSVName: "csv-c", - }, - }, - DefaultChannelName: "stable", - }, - }, - }, - args: args{ - bundle: "csv-b", - }, - expected: expected{ - err: registry.ErrRemovingDefaultChannelDuringDeprecation, - tail: nil, - }, - }, - { - /* - 0.1.2 <- 0.1.1 <- 0.1.0 - V (skips) - 1.1.2 <- 1.1.1 <- 1.1.0 - */ - description: "branchPoint", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-0.1.0", "pkg-0", []string{"0.1.x"}, newUnstructuredCSV(t, "csv-0.1.0", "")), - newBundle(t, "csv-0.1.1", "pkg-0", []string{"0.1.x", "lts"}, newUnstructuredCSV(t, "csv-0.1.1", "csv-0.1.0")), - newBundle(t, "csv-0.1.2", "pkg-0", []string{"0.1.x"}, newUnstructuredCSV(t, "csv-0.1.2", "csv-0.1.1")), - newBundle(t, "csv-1.1.0", "pkg-0", []string{"1.1.x"}, newUnstructuredCSV(t, "csv-1.1.0", "")), - newBundle(t, "csv-1.1.1", "pkg-0", []string{"1.1.x", "lts"}, newUnstructuredCSVWithSkips(t, "csv-1.1.1", "csv-1.1.0", "csv-0.1.1")), - newBundle(t, "csv-1.1.2", "pkg-0", []string{"1.1.x", "lts"}, newUnstructuredCSV(t, "csv-1.1.2", "csv-1.1.1")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "0.1.x", - CurrentCSVName: "csv-0.1.2", - }, - { - Name: "1.1.x", - CurrentCSVName: "csv-1.1.2", - }, - { - Name: "lts", - CurrentCSVName: "csv-1.1.2", - }, - }, - DefaultChannelName: "0.1.x", - }, - }, - }, - args: args{ - bundle: "csv-1.1.2", - }, - expected: expected{ - err: nil, - tail: map[string]tailBundle{ - "csv-1.1.2": {name: "csv-1.1.2", channels: []string{"1.1.x", "lts"}, replaces: []string{"csv-1.1.1"}}, - "csv-1.1.1": {name: "csv-1.1.1", channels: []string{"1.1.x", "lts"}, replaces: []string{"csv-0.1.1", "csv-1.1.0"}, replacedBy: []string{"csv-1.1.2"}}, - "csv-1.1.0": {name: "csv-1.1.0", channels: []string{"1.1.x", "lts"}, replacedBy: []string{"csv-1.1.1"}}, - "csv-0.1.1": {name: "csv-0.1.1", channels: []string{"0.1.x", "1.1.x", "lts"}, replaces: []string{"csv-0.1.0"}, replacedBy: []string{"csv-0.1.2", "csv-1.1.1"}}, // 0.1.2 present in replacedBy but not in tail - "csv-0.1.0": {name: "csv-0.1.0", channels: []string{"0.1.x"}, replacedBy: []string{"csv-0.1.1"}}, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - err = store.Migrate(context.TODO()) - require.NoError(t, err) - - for _, bundle := range tt.fields.bundles { - require.NoError(t, store.AddOperatorBundle(bundle)) - } - - for _, pkg := range tt.fields.pkgs { - require.NoError(t, store.AddPackageChannels(pkg)) - } - tx, err := db.Begin() - require.NoError(t, err) - tail, err := getTailFromBundle(tx, tt.args.bundle) - - require.Equal(t, tt.expected.err, err) - require.Equal(t, tt.expected.tail, tail) - }) - } -} - -func TestAddBundlePropertiesFromAnnotations(t *testing.T) { - mustMarshal := func(u interface{}) string { - v, err := json.Marshal(u) - require.NoError(t, err) - return string(v) - } - - type in struct { - annotations map[string]string - } - type expect struct { - err bool - } - for _, tt := range []struct { - description string - in in - expect expect - }{ - { - description: "Invalid/Properties", - in: in{ - annotations: map[string]string{ - registry.PropertyKey: "bad_properties", - }, - }, - expect: expect{ - err: true, - }, - }, - { - description: "Invalid/KnownType/Label", - in: in{ - annotations: map[string]string{ - registry.PropertyKey: fmt.Sprintf(`[{"type": "%s", "value": "bad_value"}]`, registry.LabelType), - }, - }, - expect: expect{ - err: true, - }, - }, - { - description: "Invalid/KnownType/Package", - in: in{ - annotations: map[string]string{ - registry.PropertyKey: fmt.Sprintf(`[{"type": "%s", "value": "bad_value"}]`, registry.PackageType), - }, - }, - expect: expect{ - err: true, - }, - }, - { - description: "Invalid/KnownType/GVK", - in: in{ - annotations: map[string]string{ - registry.PropertyKey: fmt.Sprintf(`[{"type": "%s", "value": "bad_value"}]`, registry.GVKType), - }, - }, - expect: expect{ - err: true, - }, - }, - { - description: "Valid/KnownTypes", - in: in{ - annotations: map[string]string{ - registry.PropertyKey: mustMarshal([]interface{}{ - registry.LabelProperty{ - Label: "sulaco", - }, - registry.PackageProperty{ - PackageName: "lv-426", - Version: "1.0.0", - }, - registry.GVKProperty{ - Group: "weyland.io", - Kind: "Dropship", - Version: "v1", - }, - registry.DeprecatedProperty{}, - }), - }, - }, - expect: expect{ - err: false, - }, - }, - { - description: "Valid/UnknownType", // Unknown types are handled as opaque blobs - in: in{ - annotations: map[string]string{ - registry.PropertyKey: fmt.Sprintf(`[{"type": "%s", "value": "anything_value"}]`, "anything"), - }, - }, - expect: expect{ - err: false, - }, - }, - } { - t.Run(tt.description, func(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - - s, err := NewSQLLiteLoader(db) - store := s.(*sqlLoader) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - tx, err := db.Begin() - require.NoError(t, err) - - csv := newUnstructuredCSV(t, "ripley", "") - csv.SetAnnotations(tt.in.annotations) - err = store.addBundleProperties(tx, newBundle(t, csv.GetName(), "lv-426", nil, csv)) - if tt.expect.err { - require.Error(t, err) - return - } - require.NoError(t, err) - }) - } -} - -func TestRemoveOverwrittenChannelHead(t *testing.T) { - type fields struct { - bundles []*registry.Bundle - pkgs []registry.PackageManifest - } - type args struct { - bundle string - pkg string - } - type expected struct { - err error - bundles map[string]struct{} - } - tests := []struct { - description string - fields fields - args args - expected expected - }{ - { - description: "ChannelHead/SingleBundlePackage", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSV(t, "csv-a", "")), - newBundle(t, "csv-b", "pkg-1", []string{"a", "b"}, newUnstructuredCSV(t, "csv-b", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-a", - }, - { - Name: "b", - CurrentCSVName: "csv-a", - }, - }, - DefaultChannelName: "a", - }, - { - PackageName: "pkg-1", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-b", - }, - { - Name: "b", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "a", - }, - }, - }, - args: args{ - bundle: "csv-a", - pkg: "pkg-0", - }, - expected: expected{ - bundles: map[string]struct{}{ - "pkg-1/a/csv-b": {}, - "pkg-1/b/csv-b": {}, - }, - }, - }, - { - description: "ChannelHead/WithReplacement", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSV(t, "csv-a", "")), - newBundle(t, "csv-aa", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-aa", "csv-a")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-a", - }, - { - Name: "b", - CurrentCSVName: "csv-aa", - }, - }, - DefaultChannelName: "b", - }, - }, - }, - args: args{ - bundle: "csv-a", - pkg: "pkg-0", - }, - expected: expected{ - err: fmt.Errorf("cannot overwrite bundle csv-a from package pkg-0: replaced by csv-aa on channel b"), - bundles: map[string]struct{}{ - "pkg-0/a/csv-a": {}, - "pkg-0/b/csv-a": {}, - "pkg-0/b/csv-aa": {}, - }, - }, - }, - { - description: "ChannelHead", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSVWithSkips(t, "csv-a", "csv-b", "csv-c")), - newBundle(t, "csv-b", "pkg-0", []string{"b", "d"}, newUnstructuredCSV(t, "csv-b", "")), - newBundle(t, "csv-d", "pkg-0", []string{"d"}, newUnstructuredCSV(t, "csv-d", "csv-b")), - newBundle(t, "csv-c", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-c", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-a", - }, - { - Name: "b", - CurrentCSVName: "csv-a", - }, - { - Name: "c", - CurrentCSVName: "csv-c", - }, - { - Name: "d", - CurrentCSVName: "csv-d", - }, - }, - DefaultChannelName: "a", - }, - }, - }, - args: args{ - bundle: "csv-a", - pkg: "pkg-0", - }, - expected: expected{ - err: nil, - bundles: map[string]struct{}{ - "pkg-0/a/csv-b": {}, - "pkg-0/b/csv-b": {}, - "pkg-0/a/csv-c": {}, - "pkg-0/b/csv-c": {}, - "pkg-0/c/csv-c": {}, - "pkg-0/d/csv-d": {}, - "pkg-0/d/csv-b": {}, - }, - }, - }, - - { - description: "PersistDefaultChannel", - fields: fields{ - bundles: []*registry.Bundle{ - newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", "")), - newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "")), - }, - pkgs: []registry.PackageManifest{ - { - PackageName: "pkg-0", - Channels: []registry.PackageChannel{ - { - Name: "a", - CurrentCSVName: "csv-a", - }, - { - Name: "b", - CurrentCSVName: "csv-b", - }, - }, - DefaultChannelName: "a", - }, - }, - }, - args: args{ - bundle: "csv-a", - pkg: "pkg-0", - }, - expected: expected{ - err: nil, - bundles: map[string]struct{}{ - "pkg-0/b/csv-b": {}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - err = store.Migrate(context.Background()) - require.NoError(t, err) - - for _, bundle := range tt.fields.bundles { - // Throw away any errors loading bundles (not testing this) - _ = store.AddOperatorBundle(bundle) - } - - for _, pkg := range tt.fields.pkgs { - // Throw away any errors loading packages (not testing this) - _ = store.AddPackageChannels(pkg) - } - - getDefaultChannel := func(pkg string) sql.NullString { - // get defaultChannel before delete - rows, err := db.QueryContext(context.Background(), `SELECT default_channel FROM package WHERE name = ?`, pkg) - require.NoError(t, err) - defer rows.Close() - var defaultChannel sql.NullString - for rows.Next() { - require.NoError(t, rows.Scan(&defaultChannel)) - break - } - return defaultChannel - } - oldDefaultChannel := getDefaultChannel(tt.args.pkg) - - err = store.(registry.HeadOverwriter).RemoveOverwrittenChannelHead(tt.args.pkg, tt.args.bundle) - if tt.expected.err != nil { - require.EqualError(t, err, tt.expected.err.Error()) - } else { - require.NoError(t, err) - } - - querier := NewSQLLiteQuerierFromDb(db) - - bundles, err := querier.ListBundles(context.Background()) - require.NoError(t, err) - - var extra []string - for _, b := range bundles { - key := fmt.Sprintf("%s/%s/%s", b.PackageName, b.ChannelName, b.CsvName) - if _, ok := tt.expected.bundles[key]; ok { - delete(tt.expected.bundles, key) - } else { - extra = append(extra, key) - } - } - - if len(tt.expected.bundles) > 0 { - t.Errorf("not all expected bundles were found: missing %v", tt.expected.bundles) - } - if len(extra) > 0 { - t.Errorf("unexpected bundles found: %v", extra) - } - - // should preserve defaultChannel entry in package table - currentDefaultChannel := getDefaultChannel(tt.args.pkg) - require.Equal(t, oldDefaultChannel, currentDefaultChannel) - }) - } -} diff --git a/pkg/sqlite/loadprocs.go b/pkg/sqlite/loadprocs.go deleted file mode 100644 index 218f2cda1..000000000 --- a/pkg/sqlite/loadprocs.go +++ /dev/null @@ -1,150 +0,0 @@ -package sqlite - -import ( - "database/sql" - "fmt" -) - -// TODO: Finish separating procedures from loader layer: make this a type to make -// unit tests more granular? -func addChannelEntry(tx *sql.Tx, channelName, packageName, csvName string, depth int) (int64, error) { - addChannelEntry, err := tx.Prepare("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)") - if err != nil { - return 0, err - } - defer addChannelEntry.Close() - - res, err := addChannelEntry.Exec(channelName, packageName, csvName, depth) - if err != nil { - return 0, fmt.Errorf("failed to insert channel_entry for channel (%s) and bundle (%s): %s", channelName, csvName, err) - } - currentID, err := res.LastInsertId() - if err != nil { - return 0, err - } - - return currentID, err -} - -func addReplaces(tx *sql.Tx, replacesID, entryID int64) error { - addReplaces, err := tx.Prepare("update channel_entry set replaces = ? where entry_id = ?") - if err != nil { - return err - } - defer addReplaces.Close() - - _, err = addReplaces.Exec(replacesID, entryID) - if err != nil { - return fmt.Errorf("failed to update channel_entry to set replaces (%d) for entry (%d): %s", replacesID, entryID, err) - } - - return nil -} - -// nolint:unused -func addPackage(tx *sql.Tx, packageName string) error { - addPackage, err := tx.Prepare("insert into package(name) values(?)") - if err != nil { - return err - } - defer addPackage.Close() - - _, err = addPackage.Exec(packageName) - if err != nil { - return fmt.Errorf("failed to insert package (%s): %s", packageName, err) - } - - return nil -} - -func addPackageIfNotExists(tx *sql.Tx, packageName string) error { - addPackage, err := tx.Prepare("insert or replace into package(name) values(?)") - if err != nil { - return err - } - defer addPackage.Close() - - _, err = addPackage.Exec(packageName) - if err != nil { - return fmt.Errorf("failed to insert or replace package (%s): %s", packageName, err) - } - - return nil -} - -// nolint:unused -func addChannel(tx *sql.Tx, channelName, packageName, headCsvName string) error { - addChannel, err := tx.Prepare("insert into channel(name, package_name, head_operatorbundle_name) values(?, ?, ?)") - if err != nil { - return err - } - defer addChannel.Close() - - _, err = addChannel.Exec(channelName, packageName, headCsvName) - if err != nil { - return fmt.Errorf("failed to insert channel (%s) for package (%s) with head (%s) : %s", channelName, packageName, headCsvName, err) - } - - return nil -} - -// nolint:unused -func updateChannel(tx *sql.Tx, channelName, packageName, headCsvName string) error { - updateChannel, err := tx.Prepare("update channel set head_operatorbundle_name = ? where name = ? and package_name = ?") - if err != nil { - return err - } - defer updateChannel.Close() - - _, err = updateChannel.Exec(channelName, packageName, headCsvName) - if err != nil { - return fmt.Errorf("failed to update channel (%s) for package (%s) with head (%s) : %s", channelName, packageName, headCsvName, err) - } - - return nil -} - -func addOrUpdateChannel(tx *sql.Tx, channelName, packageName, headCsvName string) error { - addChannel, err := tx.Prepare("insert or replace into channel(name, package_name, head_operatorbundle_name) values(?, ?, ?)") - if err != nil { - return err - } - defer addChannel.Close() - - _, err = addChannel.Exec(channelName, packageName, headCsvName) - if err != nil { - return fmt.Errorf("failed to insert or update channel (%s) for package (%s) with head (%s) : %s", channelName, packageName, headCsvName, err) - } - - return nil -} - -func updateDefaultChannel(tx *sql.Tx, channelName, packageName string) error { - updateDefaultChannel, err := tx.Prepare("update package set default_channel = ? where name = ?") - if err != nil { - return err - } - defer updateDefaultChannel.Close() - - _, err = updateDefaultChannel.Exec(channelName, packageName) - if err != nil { - return fmt.Errorf("failed to set default channel (%s) for package (%s): %s", channelName, packageName, err) - } - - return nil -} - -func truncChannelGraph(tx *sql.Tx, channelName, packageName string) error { - truncChannelGraph, err := tx.Prepare("delete from channel_entry where channel_name = ? and package_name = ?") - if err != nil { - return err - } - defer truncChannelGraph.Close() - - _, err = truncChannelGraph.Exec(channelName, packageName) - if err != nil { - return fmt.Errorf("failed to delete channel entry for channel name (%s) and package (%s): %s", channelName, packageName, err) - } - - return nil -} diff --git a/pkg/sqlite/migrations/000_init.go b/pkg/sqlite/migrations/000_init.go deleted file mode 100644 index 00986acae..000000000 --- a/pkg/sqlite/migrations/000_init.go +++ /dev/null @@ -1,78 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -var InitMigrationKey = 0 - -func init() { - registerMigration(InitMigrationKey, initMigration) -} - -var initMigration = &Migration{ - Id: InitMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - CREATE TABLE IF NOT EXISTS operatorbundle ( - name TEXT PRIMARY KEY, - csv TEXT UNIQUE, - bundle TEXT - ); - CREATE TABLE IF NOT EXISTS package ( - name TEXT PRIMARY KEY, - default_channel TEXT, - FOREIGN KEY(name, default_channel) REFERENCES channel(package_name,name) - ); - CREATE TABLE IF NOT EXISTS channel ( - name TEXT, - package_name TEXT, - head_operatorbundle_name TEXT, - PRIMARY KEY(name, package_name), - FOREIGN KEY(package_name) REFERENCES package(name), - FOREIGN KEY(head_operatorbundle_name) REFERENCES operatorbundle(name) - ); - CREATE TABLE IF NOT EXISTS channel_entry ( - entry_id INTEGER PRIMARY KEY, - channel_name TEXT, - package_name TEXT, - operatorbundle_name TEXT, - replaces INTEGER, - depth INTEGER, - FOREIGN KEY(replaces) REFERENCES channel_entry(entry_id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(channel_name, package_name) REFERENCES channel(name, package_name) - ); - CREATE TABLE IF NOT EXISTS api ( - group_name TEXT, - version TEXT, - kind TEXT, - plural TEXT NOT NULL, - PRIMARY KEY(group_name, version, kind) - ); - CREATE TABLE IF NOT EXISTS api_provider ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - ); - ` - _, err := tx.ExecContext(ctx, sql) - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - DROP TABLE operatorbundle; - DROP TABLE package; - DROP TABLE channel; - DROP TABLE channel_entry; - DROP TABLE api; - DROP TABLE api_provider; - ` - _, err := tx.ExecContext(ctx, sql) - - return err - }, -} diff --git a/pkg/sqlite/migrations/001_related_images.go b/pkg/sqlite/migrations/001_related_images.go deleted file mode 100644 index e4511bfb2..000000000 --- a/pkg/sqlite/migrations/001_related_images.go +++ /dev/null @@ -1,123 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const RelatedImagesMigrationKey = 1 - -func init() { - registerMigration(RelatedImagesMigrationKey, relatedImagesMigration) -} - -// listBundles returns a list of operatorbundles as strings -func listBundles(ctx context.Context, tx *sql.Tx) ([]string, error) { - query := "SELECT DISTINCT name FROM operatorbundle" - rows, err := tx.QueryContext(ctx, query) - if err != nil { - return nil, err - } - bundles := []string{} - for rows.Next() { - var bundleName sql.NullString - if err := rows.Scan(&bundleName); err != nil { - return nil, err - } - if bundleName.Valid { - bundles = append(bundles, bundleName.String) - } - } - return bundles, nil -} - -// getCSV pulls the csv from by name -func getCSV(ctx context.Context, tx *sql.Tx, name string) (*registry.ClusterServiceVersion, error) { - query := `SELECT csv FROM operatorbundle WHERE operatorbundle.name=?` - rows, err := tx.QueryContext(ctx, query, name) - if err != nil { - return nil, err - } - - var csvJSON sql.NullString - if !rows.Next() { - return nil, fmt.Errorf("bundle %s not found", name) - } - if err := rows.Scan(&csvJSON); err != nil { - return nil, err - } - if !csvJSON.Valid { - return nil, fmt.Errorf("bad value for csv") - } - csv := ®istry.ClusterServiceVersion{} - if err := json.Unmarshal([]byte(csvJSON.String), csv); err != nil { - return nil, err - } - return csv, nil -} - -func extractRelatedImages(ctx context.Context, tx *sql.Tx, name string) error { - addSQL := `insert into related_image(image, operatorbundle_name) values(?,?)` - csv, err := getCSV(ctx, tx, name) - if err != nil { - logrus.Warnf("error backfilling related images: %v", err) - return err - } - images, err := csv.GetOperatorImages() - if err != nil { - logrus.Warnf("error backfilling related images: %v", err) - return err - } - related, err := csv.GetRelatedImages() - if err != nil { - logrus.Warnf("error backfilling related images: %v", err) - return err - } - for k := range related { - images[k] = struct{}{} - } - for img := range images { - if _, err := tx.ExecContext(ctx, addSQL, img, name); err != nil { - logrus.Warnf("error backfilling related images: %v", err) - continue - } - } - return nil -} - -var relatedImagesMigration = &Migration{ - Id: RelatedImagesMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - CREATE TABLE IF NOT EXISTS related_image ( - image TEXT, - operatorbundle_name TEXT, - FOREIGN KEY(operatorbundle_name) REFERENCES operatorbundle(name) - ); - ` - _, _ = tx.ExecContext(ctx, sql) - - bundles, err := listBundles(ctx, tx) - if err != nil { - return err - } - for _, bundle := range bundles { - if err := extractRelatedImages(ctx, tx, bundle); err != nil { - logrus.Warnf("error backfilling related images: %v", err) - continue - } - } - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - sql := `DROP TABLE related_image;` - _, err := tx.ExecContext(ctx, sql) - return err - }, -} diff --git a/pkg/sqlite/migrations/001_related_images_test.go b/pkg/sqlite/migrations/001_related_images_test.go deleted file mode 100644 index c9336cf9b..000000000 --- a/pkg/sqlite/migrations/001_related_images_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package migrations_test - -import ( - "context" - "crypto/rand" - "database/sql" - "fmt" - "math" - "math/big" - "os" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite" - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func CreateTestDBAt(t *testing.T, key int) (*sql.DB, sqlite.Migrator, func()) { - r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - require.NoError(t, err) - - dbName := fmt.Sprintf("%d.db", r) - logrus.SetLevel(logrus.DebugLevel) - - db, err := sqlite.Open(dbName) - require.NoError(t, err) - - _, err = db.Exec("PRAGMA foreign_keys = ON", nil) - require.NoError(t, err) - - migrator, err := sqlite.NewSQLLiteMigrator(db) - require.NoError(t, err) - err = migrator.Up(context.TODO(), migrations.To(key)) - require.NoError(t, err) - return db, migrator, func() { - defer func() { - if err := os.Remove(dbName); err != nil { - t.Fatal(err) - } - }() - if err := db.Close(); err != nil { - t.Fatal(err) - } - } -} - -func TestRelatedImagesUp(t *testing.T) { - // migrate up to, but not including, this migration - db, migrator, cleanup := CreateTestDBAt(t, migrations.RelatedImagesMigrationKey-1) - defer cleanup() - - // Add a test bundle without extracting related_images - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - sql := "insert into operatorbundle(name, csv, bundle) values(?, ?, ?)" - _, err := db.ExecContext(context.TODO(), sql, "etcdoperator.v0.6.1", testCSV, testBundle) - require.NoError(t, err) - - // Up the migration with backfill - err = migrator.Up(context.TODO(), migrations.Only(migrations.RelatedImagesMigrationKey)) - require.NoError(t, err) - - // check that related images were extracted from existing bundles - querier := sqlite.NewSQLLiteQuerierFromDb(db) - images, err := querier.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - }, images) - - images, err = querier.GetImagesForBundle(context.TODO(), "etcdoperator.v0.6.1") - require.NoError(t, err) - require.ElementsMatch(t, []string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - }, images) -} - -func TestRelatedImagesDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.RelatedImagesMigrationKey) - defer cleanup() - - // Add a test bundle that has related images - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle) values(?, ?, ?)" - _, err := db.ExecContext(context.TODO(), insert, "etcdoperator.v0.6.1", testCSV, testBundle) - require.NoError(t, err) - insert = "insert into related_image(image, operatorbundle_name) values(?, ?)" - _, err = db.ExecContext(context.TODO(), insert, "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", "etcdoperator.v0.6.1") - require.NoError(t, err) - - // check that related images were extracted from existing bundles - querier := sqlite.NewSQLLiteQuerierFromDb(db) - images, err := querier.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{ - "quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943", - }, images) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.RelatedImagesMigrationKey)) - require.NoError(t, err) - - // check data removed - images, err = querier.ListImages(context.TODO()) - require.Nil(t, images) - require.Error(t, err) -} diff --git a/pkg/sqlite/migrations/002_bundle_path.go b/pkg/sqlite/migrations/002_bundle_path.go deleted file mode 100644 index b16c13d76..000000000 --- a/pkg/sqlite/migrations/002_bundle_path.go +++ /dev/null @@ -1,55 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -const BundlePathMigrationKey = 2 - -// Register this migration -func init() { - registerMigration(BundlePathMigrationKey, bundlePathMigration) -} - -var bundlePathMigration = &Migration{ - Id: BundlePathMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - ALTER TABLE operatorbundle - ADD COLUMN bundlepath TEXT; - ` - _, err := tx.ExecContext(ctx, sql) - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - foreignKeyOff := `PRAGMA foreign_keys = 0` - createTempTable := `CREATE TABLE operatorbundle_backup (name TEXT,csv TEXT,bundle TEXT)` - backupTargetTable := `INSERT INTO operatorbundle_backup SELECT name,csv,bundle FROM operatorbundle` - dropTargetTable := `DROP TABLE operatorbundle` - renameBackUpTable := `ALTER TABLE operatorbundle_backup RENAME TO operatorbundle;` - foreignKeyOn := `PRAGMA foreign_keys = 1` - _, err := tx.ExecContext(ctx, foreignKeyOff) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, createTempTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, backupTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, dropTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, renameBackUpTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, foreignKeyOn) - return err - }, -} diff --git a/pkg/sqlite/migrations/002_bundle_path_test.go b/pkg/sqlite/migrations/002_bundle_path_test.go deleted file mode 100644 index 8b13f7f18..000000000 --- a/pkg/sqlite/migrations/002_bundle_path_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package migrations_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite" - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestBundlePathUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.BundlePathMigrationKey-1) - defer cleanup() - - err := migrator.Up(context.TODO(), migrations.Only(migrations.BundlePathMigrationKey)) - require.NoError(t, err) - - // Adding row with bundlepath colum should not fail after migrating up - tx, err := db.Begin() - require.NoError(t, err) - stmt, err := tx.Prepare("insert into operatorbundle(name, csv, bundle, bundlepath) values(?, ?, ?, ?)") - require.NoError(t, err) - defer stmt.Close() - - _, err = stmt.Exec("testName", "testCSV", "testBundle", "quay.io/test") - require.NoError(t, err) -} - -func TestBundlePathDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.BundlePathMigrationKey) - defer cleanup() - - querier := sqlite.NewSQLLiteQuerierFromDb(db) - imagesBeforeMigration, err := querier.GetImagesForBundle(context.TODO(), "etcdoperator.v0.6.1") - require.NoError(t, err) - - err = migrator.Down(context.TODO(), migrations.Only(migrations.BundlePathMigrationKey)) - require.NoError(t, err) - - imagesAfterMigration, err := querier.GetImagesForBundle(context.TODO(), "etcdoperator.v0.6.1") - require.NoError(t, err) - - // Migrating down entails sensitive operations. Ensure data is preserved across down migration - require.Len(t, imagesAfterMigration, len(imagesBeforeMigration)) -} diff --git a/pkg/sqlite/migrations/003_required_apis.go b/pkg/sqlite/migrations/003_required_apis.go deleted file mode 100644 index 114e755a3..000000000 --- a/pkg/sqlite/migrations/003_required_apis.go +++ /dev/null @@ -1,146 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "fmt" - "strings" - - "github.com/sirupsen/logrus" -) - -const RequiredApiMigrationKey = 3 - -// Register this migration -func init() { - registerMigration(RequiredApiMigrationKey, requiredAPIMigration) -} - -var requiredAPIMigration = &Migration{ - Id: RequiredApiMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - CREATE TABLE IF NOT EXISTS api_requirer ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - ); - ` - _, err := tx.ExecContext(ctx, sql) - if err != nil { - return err - } - bundles, err := getChannelEntryBundles(ctx, tx) - if err != nil { - return err - } - for entryID, bundle := range bundles { - if err := extractRequiredApis(ctx, tx, entryID, bundle); err != nil { - logrus.Warnf("error backfilling required apis: %v", err) - continue - } - } - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - _, err := tx.ExecContext(ctx, `DROP TABLE api_requirer`) - if err != nil { - return err - } - - return err - }, -} - -func getChannelEntryBundles(ctx context.Context, tx *sql.Tx) (map[int64]string, error) { - query := `SELECT DISTINCT channel_entry.entry_id, operatorbundle.name FROM channel_entry - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name` - - rows, err := tx.QueryContext(ctx, query) - if err != nil { - return nil, err - } - - entries := map[int64]string{} - - for rows.Next() { - var entryID sql.NullInt64 - var name sql.NullString - if err = rows.Scan(&entryID, &name); err != nil { - return nil, err - } - if !entryID.Valid || !name.Valid { - continue - } - entries[entryID.Int64] = name.String - } - return entries, nil -} - -func extractRequiredApis(ctx context.Context, tx *sql.Tx, entryID int64, name string) error { - addAPI, err := tx.Prepare("insert or replace into api(group_name, version, kind, plural) values(?, ?, ?, ?)") - if err != nil { - return err - } - defer func() { - if err := addAPI.Close(); err != nil { - logrus.WithError(err).Warningf("error closing prepared statement") - } - }() - - addAPIRequirer, err := tx.Prepare("insert into api_requirer(group_name, version, kind, channel_entry_id) values(?, ?, ?, ?)") - if err != nil { - return err - } - defer func() { - if err := addAPIRequirer.Close(); err != nil { - logrus.WithError(err).Warningf("error closing prepared statement") - } - }() - - csv, err := getCSV(ctx, tx, name) - if err != nil { - logrus.Warnf("error backfilling required apis: %v", err) - return err - } - - _, requiredCRDs, _ := csv.GetCustomResourceDefintions() - for _, crd := range requiredCRDs { - plural, group, err := SplitCRDName(crd.Name) - if err != nil { - return err - } - if _, err := addAPI.Exec(group, crd.Version, crd.Kind, plural); err != nil { - return err - } - if _, err := addAPIRequirer.Exec(group, crd.Version, crd.Kind, entryID); err != nil { - return err - } - } - - _, requiredAPIs, _ := csv.GetApiServiceDefinitions() - for _, api := range requiredAPIs { - if _, err := addAPI.Exec(api.Group, api.Version, api.Kind, api.Name); err != nil { - return err - } - if _, err := addAPIRequirer.Exec(api.Group, api.Version, api.Kind, entryID); err != nil { - return err - } - } - - return nil -} - -func SplitCRDName(crdName string) (string, string, error) { - var err error - pluralGroup := strings.SplitN(crdName, ".", 2) - if len(pluralGroup) != 2 { - err = fmt.Errorf("can't split bad CRD name %s", crdName) - return "", "", err - } - - return pluralGroup[0], pluralGroup[1], nil -} diff --git a/pkg/sqlite/migrations/003_required_apis_test.go b/pkg/sqlite/migrations/003_required_apis_test.go deleted file mode 100644 index 415315160..000000000 --- a/pkg/sqlite/migrations/003_required_apis_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestRequiredApisUp(t *testing.T) { - // migrate up to, but not including, this migration - db, migrator, cleanup := CreateTestDBAt(t, migrations.RequiredApiMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - // Add a bundle without extracting required_apis - tx, err := db.Begin() - require.NoError(t, err) - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle) values(?, ?, ?)" - _, err = tx.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle) - require.NoError(t, err) - _, err = tx.Exec("insert into package(name, default_channel) values(?,?)", "etcd", "alpha") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "alpha", "etcd", "etcdoperator.v0.6.1") - require.NoError(t, err) - _, err = tx.Exec("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)", "alpha", "etcd", "etcdoperator.v0.6.1", 0) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - // check that no required apis were extracted. - requiredQuery := `SELECT DISTINCT api.group_name, api.version, api.kind, api.plural FROM api - INNER JOIN api_requirer ON (api.group_name=api_requirer.group_name AND api.version=api_requirer.version AND api.kind=api_requirer.kind) - WHERE api_requirer.channel_entry_id=?` - // check that no required apis were extracted. - _, err = db.Query(requiredQuery, 1) - require.Error(t, err) - - // Up the migration with backfill - err = migrator.Up(context.TODO(), migrations.Only(migrations.RequiredApiMigrationKey)) - require.NoError(t, err) - - // check that required apis were extracted - rows, err := db.Query(requiredQuery, 1) - require.NoError(t, err) - var group sql.NullString - var version sql.NullString - var kind sql.NullString - var plural sql.NullString - rows.Next() - require.NoError(t, rows.Scan(&group, &version, &kind, &plural)) - require.Equal(t, "etcd.database.coreos.com", group.String) - require.Equal(t, "v1beta2", version.String) - require.Equal(t, "EtcdCluster", kind.String) - require.Equal(t, "etcdclusters", plural.String) - require.NoError(t, rows.Close()) -} - -func TestRequiredApisDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.RequiredApiMigrationKey) - defer cleanup() - - // Add a required api - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - _, err = db.Exec("insert into api(group_name, version, kind, plural) values(?,?,?,?)", "etcd.database.coreos.com", "v1beta2", "EtcdCluster", "etcdclusters") - require.NoError(t, err) - _, err = db.Exec("insert into api_requirer(group_name, version, kind, channel_entry_id) values(?,?,?,?)", "etcd.database.coreos.com", "v1beta2", "EtcdCluster", 1) - require.NoError(t, err) - - // check that required apis were extracted from existing bundles - requiredQuery := `SELECT DISTINCT api.group_name, api.version, api.kind, api.plural FROM api - INNER JOIN api_requirer ON (api.group_name=api_requirer.group_name AND api.version=api_requirer.version AND api.kind=api_requirer.kind) - WHERE api_requirer.channel_entry_id=?` - - rows, err := db.Query(requiredQuery, 1) - require.NoError(t, err) - var group sql.NullString - var version sql.NullString - var kind sql.NullString - var plural sql.NullString - rows.Next() - require.NoError(t, rows.Scan(&group, &version, &kind, &plural)) - require.Equal(t, "etcd.database.coreos.com", group.String) - require.Equal(t, "v1beta2", version.String) - require.Equal(t, "EtcdCluster", kind.String) - require.Equal(t, "etcdclusters", plural.String) - require.NoError(t, rows.Close()) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.RequiredApiMigrationKey)) - require.NoError(t, err) - - // check that no required apis were extracted. - _, err = db.Query(requiredQuery, 1) - require.Error(t, err) -} diff --git a/pkg/sqlite/migrations/004_cascade_delete.go b/pkg/sqlite/migrations/004_cascade_delete.go deleted file mode 100644 index 6cdd9bed2..000000000 --- a/pkg/sqlite/migrations/004_cascade_delete.go +++ /dev/null @@ -1,453 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -var CascadeDeleteMigrationKey = 4 - -// Register this migration -func init() { - registerMigration(CascadeDeleteMigrationKey, cascadeDeleteMigration) -} - -var cascadeDeleteMigration = &Migration{ - Id: CascadeDeleteMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - foreingKeyOff := `PRAGMA foreign_keys = 0` - renameTable := func(table string) string { - return `ALTER TABLE ` + table + ` RENAME TO ` + table + `_old;` - } - createNewOperatorBundleTable := ` - CREATE TABLE operatorbundle ( - name TEXT PRIMARY KEY, - csv TEXT, - bundle TEXT, - bundlepath TEXT);` - createNewPackageTable := ` - CREATE TABLE package ( - name TEXT PRIMARY KEY, - default_channel TEXT, - FOREIGN KEY(name, default_channel) REFERENCES channel(package_name,name) ON DELETE CASCADE - );` - createNewChannelTable := ` - CREATE TABLE channel ( - name TEXT, - package_name TEXT, - head_operatorbundle_name TEXT, - PRIMARY KEY(name, package_name), - FOREIGN KEY(head_operatorbundle_name) REFERENCES operatorbundle(name) ON DELETE CASCADE - );` - createNewChannelEntryTable := ` - CREATE TABLE channel_entry ( - entry_id INTEGER PRIMARY KEY, - channel_name TEXT, - package_name TEXT, - operatorbundle_name TEXT, - replaces INTEGER, - depth INTEGER, - FOREIGN KEY(replaces) REFERENCES channel_entry(entry_id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(channel_name, package_name) REFERENCES channel(name, package_name) ON DELETE CASCADE - );` - createNewAPIProviderTable := ` - CREATE TABLE api_provider ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - PRIMARY KEY(group_name, version, kind, channel_entry_id), - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id) ON DELETE CASCADE, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - );` - createNewRelatedImageTable := ` - CREATE TABLE related_image ( - image TEXT, - operatorbundle_name TEXT, - FOREIGN KEY(operatorbundle_name) REFERENCES operatorbundle(name) ON DELETE CASCADE - );` - createNewAPIRequirerTable := ` - CREATE TABLE api_requirer ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - PRIMARY KEY(group_name, version, kind, channel_entry_id), - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id) ON DELETE CASCADE, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - );` - newTableTransfer := func(table string) string { - return `INSERT INTO ` + table + ` SELECT * FROM "` + table + `_old"` - } - dropTable := func(table string) string { - return `DROP TABLE "` + table + `_old"` - } - foreingKeyOn := `PRAGMA foreign_keys = 1` - - _, err := tx.ExecContext(ctx, foreingKeyOff) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewOperatorBundleTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewPackageTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewChannelTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewChannelEntryTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewAPIProviderTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewRelatedImageTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createNewAPIRequirerTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, newTableTransfer(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, foreingKeyOn) - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - foreingKeyOff := `PRAGMA foreign_keys = 0` - renameTable := func(table string) string { - return `ALTER TABLE ` + table + ` RENAME TO ` + table + `_old;` - } - createBackupOperatorBundleTable := ` - CREATE TABLE operatorbundle ( - name TEXT PRIMARY KEY, - csv TEXT UNIQUE, - bundle TEXT, - bundlepath TEXT);` - createBackupPackageTable := ` - CREATE TABLE IF NOT EXISTS package ( - name TEXT PRIMARY KEY, - default_channel TEXT, - FOREIGN KEY(name, default_channel) REFERENCES channel(package_name,name) - );` - createBackupChannelTable := ` - CREATE TABLE IF NOT EXISTS channel ( - name TEXT, - package_name TEXT, - head_operatorbundle_name TEXT, - PRIMARY KEY(name, package_name), - FOREIGN KEY(package_name) REFERENCES package(name), - FOREIGN KEY(head_operatorbundle_name) REFERENCES operatorbundle(name) - );` - createBackupChannelEntryTable := ` - CREATE TABLE IF NOT EXISTS channel_entry ( - entry_id INTEGER PRIMARY KEY, - channel_name TEXT, - package_name TEXT, - operatorbundle_name TEXT, - replaces INTEGER, - depth INTEGER, - FOREIGN KEY(replaces) REFERENCES channel_entry(entry_id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(channel_name, package_name) REFERENCES channel(name, package_name) - );` - createBackupAPIProviderTable := ` - CREATE TABLE IF NOT EXISTS api_provider ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - );` - createBackupRelatedImageTable := ` - CREATE TABLE IF NOT EXISTS related_image ( - image TEXT, - operatorbundle_name TEXT, - FOREIGN KEY(operatorbundle_name) REFERENCES operatorbundle(name) - );` - createBackupAPIRequirerTable := ` - CREATE TABLE IF NOT EXISTS api_requirer ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - );` - backupTableTransfer := func(table string) string { - return `INSERT INTO ` + table + ` SELECT * FROM "` + table + `_old"` - } - dropTable := func(table string) string { - return `DROP TABLE "` + table + `_old"` - } - - foreingKeyOn := `PRAGMA foreign_keys = 1` - - _, err := tx.ExecContext(ctx, foreingKeyOff) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, renameTable(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupOperatorBundleTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupPackageTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupChannelTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupChannelEntryTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupAPIProviderTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupRelatedImageTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, createBackupAPIRequirerTable) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`operatorbundle`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`package`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`channel`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`channel_entry`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`api_provider`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`related_image`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, backupTableTransfer(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, dropTable(`api_requirer`)) - if err != nil { - return err - } - - _, err = tx.ExecContext(ctx, foreingKeyOn) - return err - }, -} diff --git a/pkg/sqlite/migrations/004_cascade_delete_test.go b/pkg/sqlite/migrations/004_cascade_delete_test.go deleted file mode 100644 index 5659f671e..000000000 --- a/pkg/sqlite/migrations/004_cascade_delete_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestBeforeCascadeDeleteUp(t *testing.T) { - // migrate up to, but not including, this migration - db, _, cleanup := CreateTestDBAt(t, migrations.CascadeDeleteMigrationKey-1) - defer cleanup() - - tx, err := db.Begin() - require.NoError(t, err) - - checkMigrationInPreviousState(t, tx) -} - -func TestAfterCascadeDeleteUp(t *testing.T) { - // migrate up to, but not including, this migration - db, migrator, cleanup := CreateTestDBAt(t, migrations.CascadeDeleteMigrationKey-1) - defer cleanup() - - // run up migration - err := migrator.Up(context.TODO(), migrations.Only(migrations.CascadeDeleteMigrationKey)) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - checkMigrationInNextState(t, tx) -} - -func TestBeforeCascadeDeleteDown(t *testing.T) { - db, _, cleanup := CreateTestDBAt(t, migrations.CascadeDeleteMigrationKey) - defer cleanup() - - tx, err := db.Begin() - require.NoError(t, err) - - checkMigrationInNextState(t, tx) -} - -func TestAferCascadeDeleteDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.CascadeDeleteMigrationKey) - defer cleanup() - - // run down migration - err := migrator.Down(context.TODO(), migrations.Only(migrations.CascadeDeleteMigrationKey)) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - checkMigrationInPreviousState(t, tx) -} - -func removeWhiteSpaces(s string) string { - unwanted := []string{" ", "\n", "\t"} - for _, char := range unwanted { - s = strings.ReplaceAll(s, char, "") - } - return s -} - -func checkMigrationInPreviousState(t *testing.T, tx *sql.Tx) { - getCreateTableStatement := func(table string) string { - return `SELECT sql FROM sqlite_master where name="` + table + `"` - } - - createNewOperatorBundleTable := `CREATE TABLE operatorbundle ( - name TEXT PRIMARY KEY, - csv TEXT UNIQUE, - bundle TEXT, - bundlepath TEXT)` - createNewPackageTable := `CREATE TABLE package ( - name TEXT PRIMARY KEY, - default_channel TEXT, - FOREIGN KEY(name, default_channel) REFERENCES channel(package_name,name) - )` - createNewChannelTable := `CREATE TABLE channel ( - name TEXT, - package_name TEXT, - head_operatorbundle_name TEXT, - PRIMARY KEY(name, package_name), - FOREIGN KEY(package_name) REFERENCES package(name), - FOREIGN KEY(head_operatorbundle_name) REFERENCES operatorbundle(name) - )` - createNewChannelEntryTable := `CREATE TABLE channel_entry ( - entry_id INTEGER PRIMARY KEY, - channel_name TEXT, - package_name TEXT, - operatorbundle_name TEXT, - replaces INTEGER, - depth INTEGER, - FOREIGN KEY(replaces) REFERENCES channel_entry(entry_id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(channel_name, package_name) REFERENCES channel(name, package_name) - )` - createNewAPIProviderTable := `CREATE TABLE api_provider ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - )` - createNewRelatedImageTable := `CREATE TABLE related_image ( - image TEXT, - operatorbundle_name TEXT, - FOREIGN KEY(operatorbundle_name) REFERENCES operatorbundle(name) - )` - createNewAPIRequirerTable := `CREATE TABLE api_requirer ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - )` - var createStatement string - - table, err := tx.Query(getCreateTableStatement("operatorbundle")) - require.NoError(t, err) - hasRows := table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewOperatorBundleTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("package")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewPackageTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("channel")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewChannelTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("channel_entry")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewChannelEntryTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("api_requirer")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewAPIRequirerTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("api_provider")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewAPIProviderTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("related_image")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewRelatedImageTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) -} - -func checkMigrationInNextState(t *testing.T, tx *sql.Tx) { - getCreateTableStatement := func(table string) string { - return `SELECT sql FROM sqlite_master where name="` + table + `"` - } - createNewOperatorBundleTable := `CREATE TABLE operatorbundle ( - name TEXT PRIMARY KEY, - csv TEXT, - bundle TEXT, - bundlepath TEXT)` - createNewPackageTable := `CREATE TABLE package ( - name TEXT PRIMARY KEY, - default_channel TEXT, - FOREIGN KEY(name, default_channel) REFERENCES channel(package_name,name) ON DELETE CASCADE - )` - createNewChannelTable := `CREATE TABLE channel ( - name TEXT, - package_name TEXT, - head_operatorbundle_name TEXT, - PRIMARY KEY(name, package_name), - FOREIGN KEY(head_operatorbundle_name) REFERENCES operatorbundle(name) ON DELETE CASCADE - )` - createNewChannelEntryTable := `CREATE TABLE channel_entry ( - entry_id INTEGER PRIMARY KEY, - channel_name TEXT, - package_name TEXT, - operatorbundle_name TEXT, - replaces INTEGER, - depth INTEGER, - FOREIGN KEY(replaces) REFERENCES channel_entry(entry_id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(channel_name, package_name) REFERENCES channel(name, package_name) ON DELETE CASCADE - )` - createNewAPIProviderTable := `CREATE TABLE api_provider ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - PRIMARY KEY(group_name, version, kind, channel_entry_id), - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id) ON DELETE CASCADE, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - )` - createNewRelatedImageTable := `CREATE TABLE related_image ( - image TEXT, - operatorbundle_name TEXT, - FOREIGN KEY(operatorbundle_name) REFERENCES operatorbundle(name) ON DELETE CASCADE - )` - createNewAPIRequirerTable := `CREATE TABLE api_requirer ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - PRIMARY KEY(group_name, version, kind, channel_entry_id), - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id) ON DELETE CASCADE, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) - )` - var createStatement string - - table, err := tx.Query(getCreateTableStatement("operatorbundle")) - require.NoError(t, err) - hasRows := table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewOperatorBundleTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("package")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewPackageTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("channel")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewChannelTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("channel_entry")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewChannelEntryTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("api_requirer")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewAPIRequirerTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("api_provider")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewAPIProviderTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) - - table, err = tx.Query(getCreateTableStatement("related_image")) - require.NoError(t, err) - hasRows = table.Next() - require.True(t, hasRows) - err = table.Scan(&createStatement) - require.NoError(t, err) - require.Equal(t, removeWhiteSpaces(createNewRelatedImageTable), removeWhiteSpaces(createStatement)) - err = table.Close() - require.NoError(t, err) -} diff --git a/pkg/sqlite/migrations/005_version_skiprange.go b/pkg/sqlite/migrations/005_version_skiprange.go deleted file mode 100644 index 6a825debc..000000000 --- a/pkg/sqlite/migrations/005_version_skiprange.go +++ /dev/null @@ -1,94 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - - "github.com/sirupsen/logrus" -) - -const VersionSkipRangeMigrationKey = 5 -const SkipRangeAnnotationKey = "olm.skipRange" - -// Register this migration -func init() { - registerMigration(VersionSkipRangeMigrationKey, versionSkipRangeMigration) -} - -var versionSkipRangeMigration = &Migration{ - Id: VersionSkipRangeMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - ALTER TABLE operatorbundle - ADD COLUMN skiprange TEXT; - - ALTER TABLE operatorbundle - ADD COLUMN version TEXT; - ` - _, err := tx.ExecContext(ctx, sql) - if err != nil { - return err - } - - bundles, err := listBundles(ctx, tx) - if err != nil { - return err - } - for _, bundle := range bundles { - if err := extractVersioning(ctx, tx, bundle); err != nil { - logrus.Warnf("error backfilling versioning: %v", err) - continue - } - } - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - foreignKeyOff := `PRAGMA foreign_keys = 0` - createTempTable := `CREATE TABLE operatorbundle_backup (name TEXT, csv TEXT, bundle TEXT, bundlepath TEXT)` - backupTargetTable := `INSERT INTO operatorbundle_backup SELECT name, csv, bundle, bundlepath FROM operatorbundle` - dropTargetTable := `DROP TABLE operatorbundle` - renameBackUpTable := `ALTER TABLE operatorbundle_backup RENAME TO operatorbundle;` - foreignKeyOn := `PRAGMA foreign_keys = 1` - _, err := tx.ExecContext(ctx, foreignKeyOff) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, createTempTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, backupTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, dropTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, renameBackUpTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, foreignKeyOn) - return err - }, -} - -func extractVersioning(ctx context.Context, tx *sql.Tx, name string) error { - addSQL := `insert into operatorbundle(version, skiprange) values(?,?)` - csv, err := getCSV(ctx, tx, name) - if err != nil { - logrus.Warnf("error backfilling versioning: %v", err) - return err - } - skiprange, ok := csv.Annotations[SkipRangeAnnotationKey] - if !ok { - skiprange = "" - } - version, err := csv.GetVersion() - if err != nil { - version = "" - } - _, err = tx.ExecContext(ctx, addSQL, version, skiprange) - return err -} diff --git a/pkg/sqlite/migrations/005_version_skiprange_test.go b/pkg/sqlite/migrations/005_version_skiprange_test.go deleted file mode 100644 index 01e168497..000000000 --- a/pkg/sqlite/migrations/005_version_skiprange_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestVersioningUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.VersionSkipRangeMigrationKey-1) - defer cleanup() - - err := migrator.Up(context.TODO(), migrations.Only(migrations.VersionSkipRangeMigrationKey)) - require.NoError(t, err) - - // Adding row with version and skiprange column should not fail after migrating up - tx, err := db.Begin() - require.NoError(t, err) - stmt, err := tx.Prepare("insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange) values(?, ?, ?, ?, ?, ?)") - require.NoError(t, err) - defer stmt.Close() - - _, err = stmt.Exec("testName", "testCSV", "testBundle", "quay.io/test", "1.0.0", "< 1.0.0") - require.NoError(t, err) -} - -func TestVersioningDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.VersionSkipRangeMigrationKey) - defer cleanup() - - // Add a bundle without extracting required_apis - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, version, skiprange) values(?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "0.6.1", ">0.5.0 <0.6.1") - require.NoError(t, err) - _, err = db.Exec("insert into package(name, default_channel) values(?,?)", "etcd", "alpha") - require.NoError(t, err) - _, err = db.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "alpha", "etcd", "etcdoperator.v0.6.1") - require.NoError(t, err) - _, err = db.Exec("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)", "alpha", "etcd", "etcdoperator.v0.6.1", 0) - require.NoError(t, err) - - // check that required apis were extracted from existing bundles - rows, err := db.Query("select name, csv, bundle, version, skiprange from operatorbundle") - require.NoError(t, err) - var name sql.NullString - var csv sql.NullString - var bundle sql.NullString - var version sql.NullString - var skipRange sql.NullString - rows.Next() - require.NoError(t, rows.Scan(&name, &csv, &bundle, &version, &skipRange)) - require.Equal(t, "etcdoperator.v0.6.1", name.String) - require.Equal(t, csv.String, testCSV) - require.Equal(t, bundle.String, testBundle) - require.Equal(t, "0.6.1", version.String) - require.Equal(t, ">0.5.0 <0.6.1", skipRange.String) - require.NoError(t, rows.Close()) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.VersionSkipRangeMigrationKey)) - require.NoError(t, err) - - // check that bundle data is fine - rows, err = db.Query("select name, csv, bundle from operatorbundle") - require.NoError(t, err) - rows.Next() - require.NoError(t, rows.Scan(&name, &csv, &bundle)) - require.Equal(t, "etcdoperator.v0.6.1", name.String) - require.Equal(t, csv.String, testCSV) - require.Equal(t, bundle.String, testBundle) - require.NoError(t, rows.Close()) -} diff --git a/pkg/sqlite/migrations/006_associate_apis_with_bundle.go b/pkg/sqlite/migrations/006_associate_apis_with_bundle.go deleted file mode 100644 index 0e57e67fc..000000000 --- a/pkg/sqlite/migrations/006_associate_apis_with_bundle.go +++ /dev/null @@ -1,336 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const AssociateApisWithBundleMigrationKey = 6 - -// Register this migration -func init() { - registerMigration(AssociateApisWithBundleMigrationKey, bundleAPIMigration) -} - -// This migration moves the link between the provided and required apis table from the channel_entry to the -// bundle itself. This simplifies loading and minimizes changes that need to happen when a new bundle is -// inserted into an existing database. -// Before: -// api_provider: FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), -// api_requirer: FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), -// After: -// api_provider: FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath), -// api_requirer: FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath), - -var bundleAPIMigration = &Migration{ - Id: AssociateApisWithBundleMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - createNew := ` - CREATE TABLE api_provider_new ( - group_name TEXT, - version TEXT, - kind TEXT, - operatorbundle_name TEXT, - operatorbundle_version TEXT, - operatorbundle_path TEXT, - FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) ON DELETE CASCADE - ); - CREATE TABLE api_requirer_new ( - group_name TEXT, - version TEXT, - kind TEXT, - operatorbundle_name TEXT, - operatorbundle_version TEXT, - operatorbundle_path TEXT, - FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) ON DELETE CASCADE - ); --- these three fields are used as the target of a foreign key, so they need an index - CREATE UNIQUE INDEX pk ON operatorbundle(name, version, bundlepath); - ` - _, err := tx.ExecContext(ctx, createNew) - if err != nil { - return err - } - - insertProvided := `INSERT INTO api_provider_new(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?, ?)` - insertRequired := `INSERT INTO api_requirer_new(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?, ?)` - - bundleApis, err := mapBundlesToApisFromOldSchema(ctx, tx) - if err != nil { - return err - } - for bundle, apis := range bundleApis { - for provided := range apis.provided { - _, err := tx.ExecContext(ctx, insertProvided, provided.Group, provided.Version, provided.Kind, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return err - } - } - for required := range apis.required { - _, err := tx.ExecContext(ctx, insertRequired, required.Group, required.Version, required.Kind, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return err - } - } - } - - renameNewAndDropOld := ` - DROP TABLE api_provider; - DROP TABLE api_requirer; - ALTER TABLE api_provider_new RENAME TO api_provider; - ALTER TABLE api_requirer_new RENAME TO api_requirer; - ` - _, err = tx.ExecContext(ctx, renameNewAndDropOld) - if err != nil { - return err - } - - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - createOld := ` - CREATE TABLE api_provider_old ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) ON DELETE CASCADE - ); - CREATE TABLE api_requirer_old ( - group_name TEXT, - version TEXT, - kind TEXT, - channel_entry_id INTEGER, - FOREIGN KEY(channel_entry_id) REFERENCES channel_entry(entry_id), - FOREIGN KEY(group_name, version, kind) REFERENCES api(group_name, version, kind) ON DELETE CASCADE - ); - ` - _, err := tx.ExecContext(ctx, createOld) - if err != nil { - return err - } - - insertProvided := `INSERT INTO api_provider_old(group_name, version, kind, channel_entry_id) VALUES (?, ?, ?, ?)` - insertRequired := `INSERT INTO api_requirer_old(group_name, version, kind, channel_entry_id) VALUES (?, ?, ?, ?)` - - entryApis, err := mapChannelEntryToApisFromNewSchema(ctx, tx) - if err != nil { - return err - } - for entry, apis := range entryApis { - for provided := range apis.provided { - _, err := tx.ExecContext(ctx, insertProvided, provided.Group, provided.Version, provided.Kind, entry) - if err != nil { - return err - } - } - for required := range apis.required { - _, err := tx.ExecContext(ctx, insertRequired, required.Group, required.Version, required.Kind, entry) - if err != nil { - return err - } - } - } - - renameOldAndDrop := ` - DROP TABLE api_provider; - DROP TABLE api_requirer; - ALTER TABLE api_provider_old RENAME TO api_provider; - ALTER TABLE api_requirer_old RENAME TO api_requirer; - ` - _, err = tx.ExecContext(ctx, renameOldAndDrop) - if err != nil { - return err - } - - return err - }, -} - -type bundleKey struct { - BundlePath sql.NullString - Version sql.NullString - CsvName sql.NullString -} - -type apis struct { - provided map[registry.APIKey]struct{} - required map[registry.APIKey]struct{} -} - -func mapBundlesToApisFromOldSchema(ctx context.Context, tx *sql.Tx) (map[bundleKey]apis, error) { - bundles := map[bundleKey]apis{} - - providedQuery := `SELECT api_provider.group_name, api_provider.version, api_provider.kind, operatorbundle.name, operatorbundle.version, operatorbundle.bundlepath - FROM api_provider - INNER JOIN channel_entry ON channel_entry.entry_id = api_provider.channel_entry_id - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name` - - requiredQuery := `SELECT api_requirer.group_name, api_requirer.version, api_requirer.kind, operatorbundle.name, operatorbundle.version, operatorbundle.bundlepath - FROM api_requirer - INNER JOIN channel_entry ON channel_entry.entry_id = api_requirer.channel_entry_id - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name` - - providedRows, err := tx.QueryContext(ctx, providedQuery) - if err != nil { - return nil, err - } - for providedRows.Next() { - var group, apiVersion, kind, name, bundleVersion, path sql.NullString - - if err = providedRows.Scan(&group, &apiVersion, &kind, &name, &bundleVersion, &path); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid || !name.Valid { - continue - } - key := bundleKey{ - BundlePath: path, - Version: bundleVersion, - CsvName: name, - } - bundleApis, ok := bundles[key] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.provided[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[key] = bundleApis - } - - requiredRows, err := tx.QueryContext(ctx, requiredQuery) - if err != nil { - return nil, err - } - for requiredRows.Next() { - var group sql.NullString - var apiVersion sql.NullString - var kind sql.NullString - var name sql.NullString - var bundleVersion sql.NullString - var path sql.NullString - if err = requiredRows.Scan(&group, &apiVersion, &kind, &name, &bundleVersion, &path); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid || !name.Valid { - continue - } - key := bundleKey{ - BundlePath: path, - Version: bundleVersion, - CsvName: name, - } - bundleApis, ok := bundles[key] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.required[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[key] = bundleApis - } - - return bundles, nil -} - -func mapChannelEntryToApisFromNewSchema(ctx context.Context, tx *sql.Tx) (map[int64]apis, error) { - bundles := map[int64]apis{} - - providedQuery := `SELECT api_provider.group_name, api_provider.version, api_provider.kind, channel_entry.entry_id - FROM api_provider - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name - INNER JOIN channel_entry ON channel_entry.operatorbundle_name = api_provider.operatorbundle_name` - - requiredQuery := `SELECT api_requirer.group_name, api_requirer.version, api_requirer.kind, channel_entry.entry_id - FROM api_requirer - INNER JOIN channel_entry ON channel_entry.operatorbundle_name = api_requirer.operatorbundle_name - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name` - - providedRows, err := tx.QueryContext(ctx, providedQuery) - if err != nil { - return nil, err - } - for providedRows.Next() { - var ( - group, apiVersion, kind sql.NullString - entryID sql.NullInt64 - ) - if err = providedRows.Scan(&group, &apiVersion, &kind, &entryID); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid { - continue - } - - bundleApis, ok := bundles[entryID.Int64] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.provided[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[entryID.Int64] = bundleApis - } - - requiredRows, err := tx.QueryContext(ctx, requiredQuery) - if err != nil { - return nil, err - } - for requiredRows.Next() { - var ( - group, apiVersion, kind sql.NullString - entryID sql.NullInt64 - ) - if err = providedRows.Scan(&group, &apiVersion, &kind, &entryID); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid { - continue - } - - bundleApis, ok := bundles[entryID.Int64] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.required[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[entryID.Int64] = bundleApis - } - - return bundles, nil -} diff --git a/pkg/sqlite/migrations/006_associate_apis_with_bundle_test.go b/pkg/sqlite/migrations/006_associate_apis_with_bundle_test.go deleted file mode 100644 index de2d37069..000000000 --- a/pkg/sqlite/migrations/006_associate_apis_with_bundle_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestAssociateApisWithBundleUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.AssociateApisWithBundleMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"provided":[{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle) values(?, ?, ?)" - _, err = tx.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle) - require.NoError(t, err) - _, err = tx.Exec("insert into package(name, default_channel) values(?,?)", "etcd", "alpha") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "alpha", "etcd", "etcdoperator.v0.6.1") - require.NoError(t, err) - result, err := tx.Exec("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)", "alpha", "etcd", "etcdoperator.v0.6.1", 0) - require.NoError(t, err) - entryID, err := result.LastInsertId() - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdClusters", "etcdclusters") - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdBackups", "etcdbackups") - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdRestores", "etcdrestores") - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, channel_entry_id) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdClusters", entryID) - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, channel_entry_id) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdBackups", entryID) - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, channel_entry_id) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdRestores", entryID) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _, err = db.Exec(`PRAGMA foreign_keys = 1`) - require.NoError(t, err) - - // Use the old query to find the channel entries - oldEntries, err := oldGetChannelEntriesThatProvide(db, "etcd.database.coreos.com", "v1alpha1", "EtcdRestores") - require.NoError(t, err) - require.Len(t, oldEntries, 1) - require.Equal(t, "etcdoperator.v0.6.1", oldEntries[0].BundleName) - - // Up the migration with backfill - err = migrator.Up(context.TODO(), migrations.Only(migrations.AssociateApisWithBundleMigrationKey)) - require.NoError(t, err) - - // check that we can still query for provided apis - entries, err := newGetChannelEntriesThatProvide(db, "etcd.database.coreos.com", "v1alpha1", "EtcdRestores") - require.NoError(t, err) - require.Len(t, entries, 1) - require.Equal(t, "etcdoperator.v0.6.1", entries[0].BundleName) - - require.Equal(t, oldEntries, entries) -} - -func TestAssociateApisWithBundleDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.AssociateApisWithBundleMigrationKey) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"provided":[{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, version) values(?, ?, ?, ?)" - _, err = tx.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "0.6.1") - require.NoError(t, err) - _, err = tx.Exec("insert into package(name, default_channel) values(?,?)", "etcd", "alpha") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "alpha", "etcd", "etcdoperator.v0.6.1") - require.NoError(t, err) - _, err = tx.Exec("insert into channel_entry(channel_name, package_name, operatorbundle_name, depth) values(?, ?, ?, ?)", "alpha", "etcd", "etcdoperator.v0.6.1", 0) - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdClusters", "etcdclusters") - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdBackups", "etcdbackups") - require.NoError(t, err) - _, err = tx.Exec("insert into api(group_name, version, kind, plural) values(?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdRestores", "etcdrestores") - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdClusters", "etcdoperator.v0.6.1", "0.6.1", nil) - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdBackups", "etcdoperator.v0.6.1", "0.6.1", nil) - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)", "etcd.database.coreos.com", "v1alpha1", "EtcdRestores", "etcdoperator.v0.6.1", "0.6.1", nil) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _, err = db.Exec(`PRAGMA foreign_keys = 1`) - require.NoError(t, err) - - entriesBeforeMigration, err := newGetChannelEntriesThatProvide(db, "etcd.database.coreos.com", "v1alpha1", "EtcdRestores") - require.NoError(t, err) - - err = migrator.Down(context.TODO(), migrations.Only(migrations.AssociateApisWithBundleMigrationKey)) - require.NoError(t, err) - - entriesAfterMigration, err := oldGetChannelEntriesThatProvide(db, "etcd.database.coreos.com", "v1alpha1", "EtcdRestores") - require.NoError(t, err) - - // Migrating down entails sensitive operations. Ensure data is preserved across down migration - require.Equal(t, entriesBeforeMigration, entriesAfterMigration) -} - -func oldGetChannelEntriesThatProvide(db *sql.DB, group, version, kind string) ([]*registry.ChannelEntry, error) { - query := `SELECT DISTINCT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name, replaces.operatorbundle_name - FROM channel_entry - INNER JOIN api_provider ON channel_entry.entry_id = api_provider.channel_entry_id - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE api_provider.group_name = ? AND api_provider.version = ? AND api_provider.kind = ?` - - rows, err := db.Query(query, group, version, kind) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries = []*registry.ChannelEntry{} - - for rows.Next() { - var pkgNameSQL sql.NullString - var channelNameSQL sql.NullString - var bundleNameSQL sql.NullString - var replacesSQL sql.NullString - if err = rows.Scan(&pkgNameSQL, &channelNameSQL, &bundleNameSQL, &replacesSQL); err != nil { - return nil, err - } - - entries = append(entries, ®istry.ChannelEntry{ - PackageName: pkgNameSQL.String, - ChannelName: channelNameSQL.String, - BundleName: bundleNameSQL.String, - Replaces: replacesSQL.String, - }) - } - if len(entries) == 0 { - err = fmt.Errorf("no channel entries found that provide %s %s %s", group, version, kind) - return nil, err - } - return entries, nil -} - -func newGetChannelEntriesThatProvide(db *sql.DB, group, version, kind string) ([]*registry.ChannelEntry, error) { - query := `SELECT DISTINCT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name, replaces.operatorbundle_name - FROM channel_entry - INNER JOIN api_provider ON channel_entry.operatorbundle_name = api_provider.operatorbundle_name - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE api_provider.group_name = ? AND api_provider.version = ? AND api_provider.kind = ?` - - rows, err := db.Query(query, group, version, kind) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries = []*registry.ChannelEntry{} - - for rows.Next() { - var pkgNameSQL sql.NullString - var channelNameSQL sql.NullString - var bundleNameSQL sql.NullString - var replacesSQL sql.NullString - if err = rows.Scan(&pkgNameSQL, &channelNameSQL, &bundleNameSQL, &replacesSQL); err != nil { - return nil, err - } - - entries = append(entries, ®istry.ChannelEntry{ - PackageName: pkgNameSQL.String, - ChannelName: channelNameSQL.String, - BundleName: bundleNameSQL.String, - Replaces: replacesSQL.String, - }) - } - if len(entries) == 0 { - err = fmt.Errorf("no channel entries found that provide %s %s %s", group, version, kind) - return nil, err - } - return entries, nil -} diff --git a/pkg/sqlite/migrations/007_replaces_skips.go b/pkg/sqlite/migrations/007_replaces_skips.go deleted file mode 100644 index 2340634be..000000000 --- a/pkg/sqlite/migrations/007_replaces_skips.go +++ /dev/null @@ -1,144 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "fmt" - "strings" -) - -const ReplacesSkipsMigrationKey = 7 - -// Register this migration -func init() { - registerMigration(ReplacesSkipsMigrationKey, replacesSkipsMigration) -} - -// This migration adds a replaces and skips field to the operatorbundle table -// Two triggers are added to clean up the api table when no bundles require or provide them anymore -var replacesSkipsMigration = &Migration{ - Id: ReplacesSkipsMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - ALTER TABLE operatorbundle - ADD COLUMN replaces TEXT; - - ALTER TABLE operatorbundle - ADD COLUMN skips TEXT; - - CREATE TRIGGER api_provider_cleanup - AFTER DELETE ON api_provider - WHEN NOT EXISTS (SELECT 1 FROM api_provider JOIN api_requirer WHERE - (api_provider.group_name = OLD.group_name AND api_provider.version = OLD.version AND api_provider.kind = OLD.kind) OR - (api_requirer.group_name = OLD.group_name AND api_requirer.version = OLD.version AND api_requirer.kind = OLD.kind)) - BEGIN - DELETE FROM api WHERE group_name = OLD.group_name AND version = OLD.version AND kind = OLD.kind; - END; - - CREATE TRIGGER api_requirer_cleanup - AFTER DELETE ON api_requirer - WHEN NOT EXISTS (SELECT 1 FROM api_provider JOIN api_requirer WHERE - (api_provider.group_name = OLD.group_name AND api_provider.version = OLD.version AND api_provider.kind = OLD.kind) OR - (api_requirer.group_name = OLD.group_name AND api_requirer.version = OLD.version AND api_requirer.kind = OLD.kind)) - BEGIN - DELETE FROM api WHERE group_name = OLD.group_name AND version = OLD.version AND kind = OLD.kind; - END; - ` - _, err := tx.ExecContext(ctx, sql) - if err != nil { - return err - } - - bundles, err := listBundles(ctx, tx) - if err != nil { - return err - } - for _, bundle := range bundles { - if err := extractReplaces(ctx, tx, bundle); err != nil { - return fmt.Errorf("error backfilling replaces and skips: %v", err) - } - } - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - foreignKeyOff := `PRAGMA foreign_keys = 0` - createTempTable := `CREATE TABLE operatorbundle_backup (name TEXT, csv TEXT, bundle TEXT, bundlepath TEXT, version TEXT, skiprange TEXT)` - backupTargetTable := `INSERT INTO operatorbundle_backup SELECT name, csv, bundle, bundlepath, version, skiprange FROM operatorbundle` - dropTargetTable := `DROP TABLE operatorbundle` - renameBackUpTable := `ALTER TABLE operatorbundle_backup RENAME TO operatorbundle;` - foreignKeyOn := `PRAGMA foreign_keys = 1` - _, err := tx.ExecContext(ctx, foreignKeyOff) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, createTempTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, backupTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, dropTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, renameBackUpTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, foreignKeyOn) - return err - }, -} - -func extractReplaces(ctx context.Context, tx *sql.Tx, name string) error { - replaces, skips, err := getReplacesAndSkips(ctx, tx, name) - if err != nil { - return err - } - updateSQL := `update operatorbundle SET replaces = ?, skips = ? WHERE name = ?;` - _, err = tx.ExecContext(ctx, updateSQL, replaces, strings.Join(skips, ","), name) - return err -} - -func getReplacesAndSkips(ctx context.Context, tx *sql.Tx, name string) (string, []string, error) { - getReplacees := ` - SELECT DISTINCT replaces.operatorbundle_name - FROM channel_entry - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE channel_entry.operatorbundle_name = ? - ORDER BY channel_entry.depth ASC - ` - - rows, err := tx.QueryContext(ctx, getReplacees, name) - if err != nil { - return "", nil, fmt.Errorf("error backfilling replaces and skips: %v", err) - } - defer rows.Close() - - var replaces string - if rows.Next() { - var replaceeName sql.NullString - if err = rows.Scan(&replaceeName); err != nil { - return "", nil, err - } - if replaceeName.Valid { - replaces = replaceeName.String - } - } - - var skips []string - skips = []string{} - for rows.Next() { - var skipName sql.NullString - if err = rows.Scan(&skipName); err != nil { - return "", nil, err - } - if !skipName.Valid { - continue - } - skips = append(skips, skipName.String) - } - return replaces, skips, nil -} diff --git a/pkg/sqlite/migrations/007_replaces_skips_test.go b/pkg/sqlite/migrations/007_replaces_skips_test.go deleted file mode 100644 index 1b64b737d..000000000 --- a/pkg/sqlite/migrations/007_replaces_skips_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestReplacesSkipsUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.ReplacesSkipsMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // add test bundles - etcd061csv := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"provided":[{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - etcd061bundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - etcd090csv := `{"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.0","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"}]},"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 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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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.6.1","selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.9.0"}}` - etcd090bundle := `{"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.0","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"}]},"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 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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8","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.6.1","selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.9.0"}}{"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"}}` - etcd092csv := `{"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.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 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"}}` - etcd092bundle := `{"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.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 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"}}` - insertBundle := "insert into operatorbundle(name, csv, bundle) values(?, ?, ?)" - - _, err = tx.Exec(insertBundle, "etcdoperator.v0.6.1", etcd061csv, etcd061bundle) - require.NoError(t, err) - _, err = tx.Exec(insertBundle, "etcdoperator.v0.9.0", etcd090csv, etcd090bundle) - require.NoError(t, err) - _, err = tx.Exec(insertBundle, "etcdoperator.v0.9.2", etcd092csv, etcd092bundle) - require.NoError(t, err) - - _, err = tx.Exec("insert into package(name, default_channel) values(?,?)", "etcd", "alpha") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "alpha", "etcd", "etcdoperator.v0.9.2") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "beta", "etcd", "etcdoperator.v0.9.0") - require.NoError(t, err) - _, err = tx.Exec("insert into channel(name, package_name, head_operatorbundle_name) values(?,?,?)", "stable", "etcd", "etcdoperator.v0.9.2") - require.NoError(t, err) - - channelEntries := ` - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('1', 'alpha', 'etcd', 'etcdoperator.v0.9.2', '4', '0'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('2', 'alpha', 'etcd', 'etcdoperator.v0.9.1', '', '1'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('3', 'alpha', 'etcd', 'etcdoperator.v0.9.2', '2', '1'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('4', 'alpha', 'etcd', 'etcdoperator.v0.9.0', '5', '2'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('5', 'alpha', 'etcd', 'etcdoperator.v0.6.1', '', '3'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('6', 'alpha', 'etcd', 'etcdoperator.v0.5.0', '', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('7', 'alpha', 'etcd', 'etcdoperator.v0.6.1', '6', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('8', 'alpha', 'etcd', 'etcdoperator.v0.4.1', '', '5'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('9', 'alpha', 'etcd', 'etcdoperator.v0.6.1', '8', '5'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('10', 'alpha', 'etcd', 'etcdoperator.v0.3.2-a', '', '6'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('11', 'alpha', 'etcd', 'etcdoperator.v0.6.1', '10', '6'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('12', 'beta', 'etcd', 'etcdoperator.v0.9.0', '13', '0'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('13', 'beta', 'etcd', 'etcdoperator.v0.6.1', '', '1'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('14', 'beta', 'etcd', 'etcdoperator.v0.5.0', '', '2'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('15', 'beta', 'etcd', 'etcdoperator.v0.6.1', '14', '2'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('16', 'beta', 'etcd', 'etcdoperator.v0.4.1', '', '3'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('17', 'beta', 'etcd', 'etcdoperator.v0.6.1', '16', '3'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('18', 'beta', 'etcd', 'etcdoperator.v0.3.2-a', '', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('19', 'beta', 'etcd', 'etcdoperator.v0.6.1', '18', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('20', 'stable', 'etcd', 'etcdoperator.v0.9.2', '23', '0'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('21', 'stable', 'etcd', 'etcdoperator.v0.9.1', '', '1'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('22', 'stable', 'etcd', 'etcdoperator.v0.9.2', '21', '1'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('23', 'stable', 'etcd', 'etcdoperator.v0.9.0', '24', '2'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('24', 'stable', 'etcd', 'etcdoperator.v0.6.1', '', '3'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('25', 'stable', 'etcd', 'etcdoperator.v0.5.0', '', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('26', 'stable', 'etcd', 'etcdoperator.v0.6.1', '25', '4'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('27', 'stable', 'etcd', 'etcdoperator.v0.4.1', '', '5'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('28', 'stable', 'etcd', 'etcdoperator.v0.6.1', '27', '5'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('29', 'stable', 'etcd', 'etcdoperator.v0.3.2-a', '', '6'); - INSERT INTO "main"."channel_entry" ("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('30', 'stable', 'etcd', 'etcdoperator.v0.6.1', '29', '6'); ` - _, err = tx.Exec(channelEntries) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - _, err = db.Exec(`PRAGMA foreign_keys = 1`) - require.NoError(t, err) - - err = migrator.Up(context.TODO(), migrations.Only(migrations.ReplacesSkipsMigrationKey)) - require.NoError(t, err) - - // Adding row with replaces and skips column should not fail after migrating up - tx, err = db.Begin() - require.NoError(t, err) - stmt, err := tx.Prepare("insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)") - require.NoError(t, err) - defer stmt.Close() - _, err = stmt.Exec("testName", "newtestcsv", "newtestbundle", "quay.io/test", "1.0.0", "< 1.0.0", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - getBundle := `SELECT replaces, skips FROM operatorbundle WHERE name=?` - - type test struct { - name string - replaces string - skips string - } - tests := []test{ - { - name: "etcdoperator.v0.9.2", - replaces: "etcdoperator.v0.9.0", - skips: "etcdoperator.v0.9.1", - }, - { - name: "etcdoperator.v0.9.0", - replaces: "etcdoperator.v0.6.1", - skips: "", - }, - { - name: "etcdoperator.v0.6.1", - replaces: "", - skips: "etcdoperator.v0.5.0,etcdoperator.v0.4.1,etcdoperator.v0.3.2-a", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - rows, err := db.QueryContext(context.TODO(), getBundle, tt.name) - require.NoError(t, err) - require.True(t, rows.Next()) - var replacesSQL sql.NullString - var skipsSQL sql.NullString - require.NoError(t, rows.Scan(&replacesSQL, &skipsSQL)) - require.False(t, rows.Next()) - require.NoError(t, rows.Close()) - require.Equal(t, tt.replaces, replacesSQL.String) - require.Equal(t, tt.skips, skipsSQL.String) - }) - } -} - -func TestReplacesSkipsDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.ReplacesSkipsMigrationKey) - defer cleanup() - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err := db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.ReplacesSkipsMigrationKey)) - require.NoError(t, err) - - // check that bundle data is fine - var name sql.NullString - var csv sql.NullString - var bundle sql.NullString - rows, err := db.Query("select name, csv, bundle from operatorbundle") - require.NoError(t, err) - rows.Next() - require.NoError(t, rows.Scan(&name, &csv, &bundle)) - require.Equal(t, "etcdoperator.v0.6.1", name.String) - require.Equal(t, csv.String, testCSV) - require.Equal(t, bundle.String, testBundle) - require.NoError(t, rows.Close()) -} diff --git a/pkg/sqlite/migrations/008_dependencies.go b/pkg/sqlite/migrations/008_dependencies.go deleted file mode 100644 index 3df3bd64d..000000000 --- a/pkg/sqlite/migrations/008_dependencies.go +++ /dev/null @@ -1,119 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "encoding/json" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const DependenciesMigrationKey = 8 - -// Register this migration -func init() { - registerMigration(DependenciesMigrationKey, dependenciesMigration) -} - -var dependenciesMigration = &Migration{ - Id: DependenciesMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - CREATE TABLE IF NOT EXISTS dependencies ( - type TEXT, - value TEXT, - operatorbundle_name TEXT, - operatorbundle_version TEXT, - operatorbundle_path TEXT, - FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath) ON DELETE CASCADE - ); - ` - _, err := tx.ExecContext(ctx, sql) - if err != nil { - return err - } - - insertRequired := `INSERT INTO dependencies(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)` - - bundleApis, err := getRequiredAPIs(ctx, tx) - if err != nil { - return err - } - for bundle, apis := range bundleApis { - for required := range apis.required { - valueMap := map[string]string{ - "type": registry.GVKType, - "group": required.Group, - "version": required.Version, - "kind": required.Kind, - } - value, err := json.Marshal(valueMap) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, insertRequired, registry.GVKType, value, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return err - } - } - } - - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - _, err := tx.ExecContext(ctx, `DROP TABLE dependencies`) - if err != nil { - return err - } - - return err - }, -} - -func getRequiredAPIs(ctx context.Context, tx *sql.Tx) (map[bundleKey]apis, error) { - bundles := map[bundleKey]apis{} - - requiredQuery := `SELECT api_requirer.group_name, api_requirer.version, api_requirer.kind, api_requirer.operatorbundle_name, api_requirer.operatorbundle_version, api_requirer.operatorbundle_path - FROM api_requirer` - - requiredRows, err := tx.QueryContext(ctx, requiredQuery) - if err != nil { - return nil, err - } - for requiredRows.Next() { - var group sql.NullString - var apiVersion sql.NullString - var kind sql.NullString - var name sql.NullString - var bundleVersion sql.NullString - var path sql.NullString - if err = requiredRows.Scan(&group, &apiVersion, &kind, &name, &bundleVersion, &path); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid || !name.Valid { - continue - } - key := bundleKey{ - BundlePath: path, - Version: bundleVersion, - CsvName: name, - } - bundleApis, ok := bundles[key] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.required[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[key] = bundleApis - } - - return bundles, nil -} diff --git a/pkg/sqlite/migrations/008_dependencies_test.go b/pkg/sqlite/migrations/008_dependencies_test.go deleted file mode 100644 index 684341ef4..000000000 --- a/pkg/sqlite/migrations/008_dependencies_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestDependenciesUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.DependenciesMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - _, err = tx.Exec("insert into api_requirer(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)", "test.coreos.com", "v1", "testapi", "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Up(context.TODO(), migrations.Only(migrations.DependenciesMigrationKey)) - require.NoError(t, err) - - depQuery := `SELECT DISTINCT type, value FROM dependencies - WHERE operatorbundle_name=? AND operatorbundle_version=? AND operatorbundle_path=?` - - rows, err := db.Query(depQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - defer rows.Close() - rows.Next() - var typeName sql.NullString - var value sql.NullString - require.NoError(t, rows.Scan(&typeName, &value)) - require.Equal(t, "olm.gvk", typeName.String) - require.JSONEq(t, `{"group":"test.coreos.com","kind":"testapi","type":"olm.gvk","version":"v1"}`, value.String) - require.NoError(t, rows.Close()) -} - -func TestDependenciesDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.DependenciesMigrationKey) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\nComing soon, the ability to schedule backups to happen on or off cluster.\n","displayName":"etcd","icon":[{"base64data":"iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC","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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - valueStr := `{"packageName":"etcd-operator","type":"olm.package","version":">0.6.0"}` - _, err = db.Exec("insert into dependencies(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)", "olm.package", valueStr, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - depQuery := `SELECT DISTINCT type, value FROM dependencies - WHERE operatorbundle_name=? AND operatorbundle_version=? AND operatorbundle_path=?` - - rows, err := db.Query(depQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - defer rows.Close() - rows.Next() - var typeName sql.NullString - var value sql.NullString - require.NoError(t, rows.Scan(&typeName, &value)) - require.Equal(t, "olm.package", typeName.String) - require.Equal(t, value.String, valueStr) - require.NoError(t, rows.Close()) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.DependenciesMigrationKey)) - require.NoError(t, err) - - // check that no dependencies were extracted, since table is now gone - _, err = db.Query(depQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.Error(t, err) -} diff --git a/pkg/sqlite/migrations/009_properties.go b/pkg/sqlite/migrations/009_properties.go deleted file mode 100644 index 252ad99ec..000000000 --- a/pkg/sqlite/migrations/009_properties.go +++ /dev/null @@ -1,166 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "encoding/json" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const PropertiesMigrationKey = 9 - -// Register this migration -func init() { - registerMigration(PropertiesMigrationKey, propertiesMigration) -} - -var propertiesMigration = &Migration{ - Id: PropertiesMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - CREATE TABLE IF NOT EXISTS properties ( - type TEXT, - value TEXT, - operatorbundle_name TEXT, - operatorbundle_version TEXT, - operatorbundle_path TEXT, - FOREIGN KEY(operatorbundle_name, operatorbundle_version, operatorbundle_path) REFERENCES operatorbundle(name, version, bundlepath) ON DELETE CASCADE - ); - ` - _, err := tx.ExecContext(ctx, sql) - if err != nil { - return err - } - - insertProperty := `INSERT INTO properties(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)` - - bundleApis, err := getProvidedAPIs(ctx, tx) - if err != nil { - return err - } - for bundle, apis := range bundleApis { - pkg, err := getPackageForBundle(ctx, bundle.CsvName.String, tx) - if err != nil { - return err - } - valueMap := map[string]string{ - "packageName": pkg, - "version": bundle.Version.String, - } - value, err := json.Marshal(valueMap) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, insertProperty, registry.PackageType, value, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return err - } - - for provided := range apis.provided { - valueMap := map[string]string{ - "group": provided.Group, - "version": provided.Version, - "kind": provided.Kind, - } - value, err := json.Marshal(valueMap) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, insertProperty, registry.GVKType, value, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return err - } - } - } - - // update the serialized value to omit the dependency type - updateDependencySQL := ` - UPDATE dependencies - SET value = (SELECT json_remove(value, "$.type") - FROM dependencies - WHERE operatorbundle_name=dependencies.operatorbundle_name)` - _, err = tx.ExecContext(ctx, updateDependencySQL) - if err != nil { - return err - } - - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - _, err := tx.ExecContext(ctx, `DROP TABLE properties`) - if err != nil { - return err - } - - return err - }, -} - -func getPackageForBundle(ctx context.Context, name string, tx *sql.Tx) (string, error) { - packageQuery := `SELECT DISTINCT package_name FROM channel_entry WHERE channel_entry.operatorbundle_name=?` - packageRows, err := tx.QueryContext(ctx, packageQuery, name) - if err != nil { - return "", err - } - for packageRows.Next() { - var pkg sql.NullString - if err = packageRows.Scan(&pkg); err != nil { - return "", err - } - if !pkg.Valid { - return "", err - } - // nolint: staticcheck - return pkg.String, nil - } - return "", err -} - -func getProvidedAPIs(ctx context.Context, tx *sql.Tx) (map[bundleKey]apis, error) { - bundles := map[bundleKey]apis{} - - providedQuery := `SELECT group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path - FROM api_provider` - - providedRows, err := tx.QueryContext(ctx, providedQuery) - if err != nil { - return nil, err - } - for providedRows.Next() { - var group sql.NullString - var apiVersion sql.NullString - var kind sql.NullString - var name sql.NullString - var bundleVersion sql.NullString - var path sql.NullString - if err = providedRows.Scan(&group, &apiVersion, &kind, &name, &bundleVersion, &path); err != nil { - return nil, err - } - if !group.Valid || !apiVersion.Valid || !kind.Valid || !name.Valid { - continue - } - key := bundleKey{ - BundlePath: path, - Version: bundleVersion, - CsvName: name, - } - bundleApis, ok := bundles[key] - if !ok { - bundleApis = apis{ - provided: map[registry.APIKey]struct{}{}, - required: map[registry.APIKey]struct{}{}, - } - } - - bundleApis.provided[registry.APIKey{ - Group: group.String, - Version: apiVersion.String, - Kind: kind.String, - }] = struct{}{} - - bundles[key] = bundleApis - } - - return bundles, nil -} diff --git a/pkg/sqlite/migrations/009_properties_test.go b/pkg/sqlite/migrations/009_properties_test.go deleted file mode 100644 index 7c17866a6..000000000 --- a/pkg/sqlite/migrations/009_properties_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestPropertiesUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.PropertiesMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - _, err = tx.Exec("insert into api_provider(group_name, version, kind, operatorbundle_name, operatorbundle_version, operatorbundle_path) values(?, ?, ?, ?, ?, ?)", "test.coreos.com", "v1", "testapi", "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - channelEntries := `INSERT INTO channel_entry("entry_id", "channel_name", "package_name", "operatorbundle_name", "replaces", "depth") VALUES ('1', 'alpha', 'etcd', 'etcdoperator.v0.6.1', '', '0');` - _, err = tx.Exec(channelEntries) - require.NoError(t, err) - valueStr := `{"packageName":"etcd","type":"olm.package","version":">0.6.0"}` - _, err = tx.Exec("insert into dependencies(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)", "olm.package", valueStr, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Up(context.TODO(), migrations.Only(migrations.PropertiesMigrationKey)) - require.NoError(t, err) - - t.Run("verify properties", func(t *testing.T) { - propQuery := `SELECT DISTINCT type, value FROM properties - WHERE operatorbundle_name=? AND operatorbundle_version=? AND operatorbundle_path=?` - - rows, err := db.Query(propQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - defer rows.Close() - - type prop struct { - typeName string - value string - } - properties := []prop{} - for rows.Next() { - var typeName sql.NullString - var value sql.NullString - require.NoError(t, rows.Scan(&typeName, &value)) - require.True(t, typeName.Valid) - require.True(t, value.Valid) - properties = append(properties, prop{typeName: typeName.String, value: value.String}) - } - - expectedProperties := []prop{ - { - typeName: "olm.package", - value: `{"packageName":"etcd","version":"0.6.1"}`, - }, - { - typeName: "olm.gvk", - value: `{"group":"test.coreos.com","kind":"testapi","version":"v1"}`, - }, - } - require.Equal(t, expectedProperties, properties) - }) - - t.Run("verify dependencies", func(t *testing.T) { - depQuery := `SELECT DISTINCT type, value FROM dependencies` - rows, err := db.Query(depQuery) - require.NoError(t, err) - defer rows.Close() - - type dep struct { - typeName string - value string - } - deps := []dep{} - for rows.Next() { - var typeName sql.NullString - var value sql.NullString - require.NoError(t, rows.Scan(&typeName, &value)) - require.True(t, typeName.Valid) - require.True(t, value.Valid) - deps = append(deps, dep{typeName: typeName.String, value: value.String}) - } - - expectedDeps := []dep{ - { - typeName: "olm.package", - value: `{"packageName":"etcd","version":">0.6.0"}`, - }, - } - require.Equal(t, expectedDeps, deps) - }) -} - -func TestPropertiesDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.PropertiesMigrationKey) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - valueStr := `{"packageName":"etcd-operator","version":">0.6.0"}` - _, err = db.Exec("insert into properties(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)", "olm.package", valueStr, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - propQuery := `SELECT DISTINCT type, value FROM properties - WHERE operatorbundle_name=? AND operatorbundle_version=? AND operatorbundle_path=?` - - rows, err := db.Query(propQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.NoError(t, err) - defer rows.Close() - rows.Next() - var typeName sql.NullString - var value sql.NullString - require.NoError(t, rows.Scan(&typeName, &value)) - require.Equal(t, "olm.package", typeName.String) - require.Equal(t, value.String, valueStr) - require.NoError(t, rows.Close()) - - // run down migration - err = migrator.Down(context.TODO(), migrations.Only(migrations.PropertiesMigrationKey)) - require.NoError(t, err) - - // check that no properties were extracted, as properties table no longer exists - _, err = db.Query(propQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.Error(t, err) -} diff --git a/pkg/sqlite/migrations/010_set_bundlepath_pkg_property.go b/pkg/sqlite/migrations/010_set_bundlepath_pkg_property.go deleted file mode 100644 index d488775b0..000000000 --- a/pkg/sqlite/migrations/010_set_bundlepath_pkg_property.go +++ /dev/null @@ -1,42 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -const BundlePathPkgMigrationKey = 10 - -// Register this migration -func init() { - registerMigration(BundlePathPkgMigrationKey, bundlePathPkgPropertyMigration) -} - -var bundlePathPkgPropertyMigration = &Migration{ - Id: BundlePathPkgMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - updatePropertiesSQL := ` - UPDATE properties - SET operatorbundle_path = (SELECT bundlepath - FROM operatorbundle - WHERE operatorbundle_name = operatorbundle.name AND operatorbundle_version = operatorbundle.version)` - _, err := tx.ExecContext(ctx, updatePropertiesSQL) - if err != nil { - return err - } - - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - updatePropertiesSQL := ` - UPDATE properties - SET operatorbundle_path = null - WHERE type = "olm.package"` - _, err := tx.ExecContext(ctx, updatePropertiesSQL) - if err != nil { - return err - } - - return err - }, -} diff --git a/pkg/sqlite/migrations/010_set_bundlepath_pkg_property_test.go b/pkg/sqlite/migrations/010_set_bundlepath_pkg_property_test.go deleted file mode 100644 index e1465d146..000000000 --- a/pkg/sqlite/migrations/010_set_bundlepath_pkg_property_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestBundlePathPropertyUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.BundlePathPkgMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - _, err = tx.Exec("insert into properties(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)", "olm.package", `{}`, "etcdoperator.v0.6.1", "0.6.1", "") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Up(context.TODO(), migrations.Only(migrations.BundlePathPkgMigrationKey)) - require.NoError(t, err) - - t.Run("verify properties", func(t *testing.T) { - propQuery := `SELECT operatorbundle_path FROM properties - WHERE operatorbundle_name=? AND operatorbundle_version=?` - - rows, err := db.Query(propQuery, "etcdoperator.v0.6.1", "0.6.1") - require.NoError(t, err) - defer rows.Close() - rows.Next() - var bundlepath sql.NullString - require.NoError(t, rows.Scan(&bundlepath)) - require.Equal(t, "quay.io/image", bundlepath.String) - }) -} - -func TestBundlePathPropertyDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.BundlePathPkgMigrationKey) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - _, err = tx.Exec("insert into properties(type, value, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?, ?)", "olm.package", `{}`, "etcdoperator.v0.6.1", "0.6.1", "") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Down(context.TODO(), migrations.Only(migrations.BundlePathPkgMigrationKey)) - require.NoError(t, err) - - t.Run("verify properties", func(t *testing.T) { - propQuery := `SELECT operatorbundle_path FROM properties - WHERE operatorbundle_name=? AND operatorbundle_version=? AND operatorbundle_path=?` - - rows, _ := db.Query(propQuery, "etcdoperator.v0.6.1", "0.6.1", "quay.io/image") - require.False(t, rows.Next()) - }) -} diff --git a/pkg/sqlite/migrations/011_substitutes_for_test.go b/pkg/sqlite/migrations/011_substitutes_for_test.go deleted file mode 100644 index 84e125ba9..000000000 --- a/pkg/sqlite/migrations/011_substitutes_for_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestSubstitutesForUp(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.SubstitutesForMigrationKey-1) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips) values(?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Up(context.TODO(), migrations.Only(migrations.SubstitutesForMigrationKey)) - require.NoError(t, err) - // Check operatorbundle table substitutesfor field empty - query := `SELECT substitutesfor FROM operatorbundle WHERE name=?` - rows, err := db.Query(query, "etcdoperator.v0.6.1") - require.NoError(t, err) - defer rows.Close() - - var substitutesFor sql.NullString - if rows.Next() { - require.NoError(t, rows.Scan(&substitutesFor)) - } - require.Empty(t, substitutesFor.String) -} - -func TestSubstitutesForDown(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.SubstitutesForMigrationKey) - defer cleanup() - - _, err := db.Exec(`PRAGMA foreign_keys = 0`) - require.NoError(t, err) - - tx, err := db.Begin() - require.NoError(t, err) - - // Add a bundle - testCSV := `{"apiVersion":"operators.coreos.com/v1alpha1","kind":"ClusterServiceVersion","metadata":{"annotations":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - testBundle := `{"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":{"tectonic-visibility":"ocs"},"name":"etcdoperator.v0.6.1","namespace":"placeholder"},"spec":{"customresourcedefinitions":{"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"]}],"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":"service","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":"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 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**High availability**\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**Automated updates**\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**Backups included**\nComing soon, the ability to schedule backups to happen on or off cluster.\n","displayName":"etcd","icon":[{"base64data":"iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC","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:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943","name":"etcd-operator"}],"serviceAccountName":"etcd-operator"}}}}],"permissions":[{"rules":[{"apiGroups":["etcd.database.coreos.com"],"resources":["etcdclusters"],"verbs":["*"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"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","alm-status-descriptors":"etcdoperator.v0.6.1","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"},"selector":{"matchLabels":{"alm-owner-etcd":"etcdoperator","operated-by":"etcdoperator"}},"version":"0.6.1"}}` - insert := "insert into operatorbundle(name, csv, bundle, bundlepath, version, skiprange, replaces, skips, substitutesfor) values(?, ?, ?, ?, ?, ?, ?, ?, ?)" - _, err = db.Exec(insert, "etcdoperator.v0.6.1", testCSV, testBundle, "quay.io/image", "0.6.1", ">0.5.0 <0.6.1", "0.9.0", "0.9.1,0.9.2", "dummy") - require.NoError(t, err) - require.NoError(t, tx.Commit()) - - err = migrator.Down(context.TODO(), migrations.Only(migrations.SubstitutesForMigrationKey)) - require.NoError(t, err) -} diff --git a/pkg/sqlite/migrations/011_susbtitutes_for.go b/pkg/sqlite/migrations/011_susbtitutes_for.go deleted file mode 100644 index 9b199579c..000000000 --- a/pkg/sqlite/migrations/011_susbtitutes_for.go +++ /dev/null @@ -1,55 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -const SubstitutesForMigrationKey = 11 - -// Register this migration -func init() { - registerMigration(SubstitutesForMigrationKey, substitutesForPropertyMigration) -} - -var substitutesForPropertyMigration = &Migration{ - Id: SubstitutesForMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - sql := ` - ALTER TABLE operatorbundle - ADD COLUMN substitutesfor TEXT; - ` - _, err := tx.ExecContext(ctx, sql) - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - foreignKeyOff := `PRAGMA foreign_keys = 0` - createTempTable := `CREATE TABLE operatorbundle_backup (name TEXT, csv TEXT, bundle TEXT, bundlepath TEXT, version TEXT, skiprange TEXT, replaces TEXT, skips TEXT)` - backupTargetTable := `INSERT INTO operatorbundle_backup SELECT name, csv, bundle, bundlepath, version, skiprange, replaces, skips FROM operatorbundle` - dropTargetTable := `DROP TABLE operatorbundle` - renameBackUpTable := `ALTER TABLE operatorbundle_backup RENAME TO operatorbundle;` - foreignKeyOn := `PRAGMA foreign_keys = 1` - _, err := tx.ExecContext(ctx, foreignKeyOff) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, createTempTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, backupTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, dropTargetTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, renameBackUpTable) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, foreignKeyOn) - return err - }, -} diff --git a/pkg/sqlite/migrations/012_deprecated.go b/pkg/sqlite/migrations/012_deprecated.go deleted file mode 100644 index e99480d58..000000000 --- a/pkg/sqlite/migrations/012_deprecated.go +++ /dev/null @@ -1,43 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "fmt" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -const DeprecatedMigrationKey = 12 - -// Register this migration -func init() { - registerMigration(DeprecatedMigrationKey, deprecatedMigration) -} - -var deprecatedMigration = &Migration{ - Id: DeprecatedMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - // Purposefully forego a foreign key constraint so this table can survive operations that drop bundles and properties - // e.g. a lossy implementation of --overwrite-latest that relies on readding all bundles in a package - sql := ` - CREATE TABLE IF NOT EXISTS deprecated ( - operatorbundle_name TEXT PRIMARY KEY - ); - ` - if _, err := tx.ExecContext(ctx, sql); err != nil { - return err - } - - // nolint: gosec - initDeprecated := fmt.Sprintf(`INSERT OR REPLACE INTO deprecated(operatorbundle_name) SELECT operatorbundle_name FROM properties WHERE properties.type='%s'`, registry.DeprecatedType) - _, err := tx.ExecContext(ctx, initDeprecated) - - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - _, err := tx.ExecContext(ctx, `DROP TABLE deprecated`) - - return err - }, -} diff --git a/pkg/sqlite/migrations/012_deprecated_test.go b/pkg/sqlite/migrations/012_deprecated_test.go deleted file mode 100644 index 0d57de955..000000000 --- a/pkg/sqlite/migrations/012_deprecated_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestDeprecated(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.DeprecatedMigrationKey-1) - defer cleanup() - - // Insert fixture bundles to satisfy foreign key constraint in properties table - insertBundle := "INSERT INTO operatorbundle(name, version, bundlepath, csv) VALUES (?, ?, ?, ?)" - insertProperty := "INSERT INTO properties(type, operatorbundle_name, operatorbundle_version, operatorbundle_path) VALUES (?, ?, ?, ?)" - - // Add unique bundles both with and without the deprecated property - // The content of the bundles is otherwise unimportant - // Deprecated: - _, err := db.Exec(insertBundle, "operator.v1.0.0", "1.0.0", "quay.io/operator:v1.0.0", "operator.v1.0.0's csv") - require.NoError(t, err) - _, err = db.Exec(insertProperty, registry.DeprecatedType, "operator.v1.0.0", "1.0.0", "quay.io/operator:v1.0.0") - require.NoError(t, err) - // Add a duplicate deprecated property to ensure idempotency of the migration - _, err = db.Exec(insertProperty, registry.DeprecatedType, "operator.v1.0.0", "1.0.0", "quay.io/operator:v1.0.0") - require.NoError(t, err) - _, err = db.Exec(insertProperty, "extraneous", "operator.v1.0.0", "1.0.0", "quay.io/operator:v1.0.0") - require.NoError(t, err) - - // Not deprecated: - _, err = db.Exec(insertBundle, "operator.v2.0.0", "2.0.0", "quay.io/operator:v2.0.0", "operator.v2.0.0's csv") - require.NoError(t, err) - _, err = db.Exec(insertProperty, "extraneous", "operator.v2.0.0", "2.0.0", "quay.io/operator:v2.0.0") - require.NoError(t, err) - - // This migration should populate the deprecated table with the names of all bundles that have the deprecated property - require.NoError(t, migrator.Up(context.Background(), migrations.Only(migrations.DeprecatedMigrationKey))) - - deprecated, err := db.Query("SELECT * FROM deprecated") - require.NoError(t, err) - defer deprecated.Close() - - require.True(t, deprecated.Next(), "failed to detect deprecated bundle") - var name sql.NullString - require.NoError(t, deprecated.Scan(&name)) - require.True(t, name.Valid) - require.Equal(t, "operator.v1.0.0", name.String) - require.False(t, deprecated.Next(), "incorrect number of deprecated bundles") - - // This migration should drop the deprecated table - require.NoError(t, migrator.Down(context.Background(), migrations.Only(migrations.DeprecatedMigrationKey))) - - table, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='deprecated'") - require.NoError(t, err) - defer table.Close() - require.False(t, table.Next(), "deprecated table wasn't properly cleaned up on downgrade") -} diff --git a/pkg/sqlite/migrations/013_rm_truncated_deprecations.go b/pkg/sqlite/migrations/013_rm_truncated_deprecations.go deleted file mode 100644 index 75ad31517..000000000 --- a/pkg/sqlite/migrations/013_rm_truncated_deprecations.go +++ /dev/null @@ -1,31 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" -) - -const RmTruncatedDeprecationsMigrationKey = 13 - -// Register this migration -func init() { - registerMigration(RmTruncatedDeprecationsMigrationKey, rmTruncatedDeprecationsMigration) -} - -var rmTruncatedDeprecationsMigration = &Migration{ - Id: RmTruncatedDeprecationsMigrationKey, - Up: func(ctx context.Context, tx *sql.Tx) error { - - // Delete deprecation history for all bundles that no longer exist in the operatorbundle table - // These bundles have been truncated by more recent deprecations and would only confuse future operations on an index; - // e.g. adding a previously truncated bundle to a package removed via `opm index|registry rm` would lead to that bundle - // being deprecated - _, err := tx.ExecContext(ctx, `DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN operatorbundle ON deprecated.operatorbundle_name = operatorbundle.name))`) - - return err - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - // No-op - return nil - }, -} diff --git a/pkg/sqlite/migrations/013_rm_truncated_deprecations_test.go b/pkg/sqlite/migrations/013_rm_truncated_deprecations_test.go deleted file mode 100644 index 5ed881515..000000000 --- a/pkg/sqlite/migrations/013_rm_truncated_deprecations_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package migrations_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestRmTruncatedDeprecations(t *testing.T) { - db, migrator, cleanup := CreateTestDBAt(t, migrations.RmTruncatedDeprecationsMigrationKey-1) - defer cleanup() - - // Insert fixtures to satisfy foreign key constraints - insertBundle := "INSERT INTO operatorbundle(name, version, bundlepath, csv) VALUES (?, ?, ?, ?)" - insertChannel := "INSERT INTO channel(name, package_name, head_operatorbundle_name) VALUES (?, ?, ?)" - insertChannelEntry := "INSERT INTO channel_entry(entry_id, channel_name, package_name, operatorbundle_name) VALUES (?, ?, ?, ?)" - insertDeprecated := "INSERT INTO deprecated(operatorbundle_name) VALUES (?)" - - // Add a deprecated bundle - _, err := db.Exec(insertBundle, "operator.v1.0.0", "1.0.0", "quay.io/operator:v1.0.0", "operator.v1.0.0's csv") - require.NoError(t, err) - _, err = db.Exec(insertChannel, "stable", "apple", "operator.v1.0.0") - require.NoError(t, err) - _, err = db.Exec(insertChannelEntry, 0, "stable", "apple", "operator.v1.0.0") - require.NoError(t, err) - _, err = db.Exec(insertDeprecated, "operator.v1.0.0") - require.NoError(t, err) - - // Add a truncated bundle; i.e. doesn't exist in the channel_entry table - _, err = db.Exec(insertDeprecated, "operator.v1.0.0-pre") - require.NoError(t, err) - - // This migration should delete all bundles that are not referenced by the channel_entry table - require.NoError(t, migrator.Up(context.Background(), migrations.Only(migrations.RmTruncatedDeprecationsMigrationKey))) - - deprecated, err := db.Query("SELECT * FROM deprecated") - require.NoError(t, err) - defer deprecated.Close() - - require.True(t, deprecated.Next(), "failed to detect deprecated bundle") - var name sql.NullString - require.NoError(t, deprecated.Scan(&name)) - require.True(t, name.Valid) - require.Equal(t, "operator.v1.0.0", name.String) - require.False(t, deprecated.Next(), "incorrect number of deprecated bundles") -} diff --git a/pkg/sqlite/migrations/migrations.go b/pkg/sqlite/migrations/migrations.go deleted file mode 100644 index f978196de..000000000 --- a/pkg/sqlite/migrations/migrations.go +++ /dev/null @@ -1,93 +0,0 @@ -package migrations - -import ( - "context" - "database/sql" - "fmt" - "sort" -) - -type Migration struct { - Id int - Up func(context.Context, *sql.Tx) error - Down func(context.Context, *sql.Tx) error -} - -type MigrationSet map[int]*Migration - -type Migrations []*Migration - -func (m Migrations) Len() int { return len(m) } -func (m Migrations) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m Migrations) Less(i, j int) bool { return m[i].Id < m[j].Id } - -var migrations MigrationSet = make(map[int]*Migration) - -// From returns a set of migrations, starting at key -func (m MigrationSet) From(key int) Migrations { - keys := make([]int, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Ints(keys) - sorted := []*Migration{} - for _, k := range keys { - if k < key { - continue - } - sorted = append(sorted, m[k]) - } - return sorted -} - -// To returns a set of migrations, up to and including key -func (m MigrationSet) To(key int) Migrations { - keys := make([]int, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Ints(keys) - sorted := []*Migration{} - for _, k := range keys { - if k > key { - continue - } - sorted = append(sorted, m[k]) - } - return sorted -} - -// Only returns a set of one migration -func (m MigrationSet) Only(key int) Migrations { - return []*Migration{m[key]} -} - -// From returns a set of migrations, starting at key -func From(key int) Migrations { - return migrations.From(key) -} - -// To returns a set of migrations, up to and including key -func To(key int) Migrations { - return migrations.To(key) -} - -// Only returns a set of one migration -func Only(key int) Migrations { - return migrations.Only(key) -} - -// All returns the full set -func All() MigrationSet { - return migrations -} - -func registerMigration(key int, m *Migration) { - if _, ok := migrations[key]; ok { - panic(fmt.Sprintf("already have a migration registered with id %d", key)) - } - if m.Id != key { - panic(fmt.Sprintf("migration has wrong id for key. key: %d, id: %d", key, m.Id)) - } - migrations[key] = m -} diff --git a/pkg/sqlite/migrator.go b/pkg/sqlite/migrator.go deleted file mode 100644 index 9f1438ab5..000000000 --- a/pkg/sqlite/migrator.go +++ /dev/null @@ -1,213 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - _ "github.com/golang-migrate/migrate/v4/source/file" // indirect import required by golang-migrate package - "github.com/sirupsen/logrus" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -type Migrator interface { - Migrate(ctx context.Context) error - Up(ctx context.Context, migrations migrations.Migrations) error - Down(ctx context.Context, migrations migrations.Migrations) error -} - -type SQLLiteMigrator struct { - db *sql.DB - migrationsTable string - migrations migrations.MigrationSet -} - -var _ Migrator = &SQLLiteMigrator{} - -const ( - DefaultMigrationsTable = "schema_migrations" - NilVersion = -1 -) - -// NewSQLLiteMigrator returns a SQLLiteMigrator. -func NewSQLLiteMigrator(db *sql.DB) (Migrator, error) { - return &SQLLiteMigrator{ - db: db, - migrationsTable: DefaultMigrationsTable, - migrations: migrations.All(), - }, nil -} - -// Migrate gets the current version from the database, the latest version from the migrations, -// and migrates up the the latest -func (m *SQLLiteMigrator) Migrate(ctx context.Context) error { - tx, err := m.db.Begin() - if err != nil { - return err - } - defer func() { - if err := tx.Rollback(); err != nil && !strings.Contains(err.Error(), "transaction has already been committed") { - logrus.WithError(err).Warnf("couldn't rollback") - } - }() - - version, err := m.version(ctx, tx) - if err != nil { - return err - } - - if err := tx.Commit(); err != nil { - return tx.Rollback() - } - return m.Up(ctx, m.migrations.From(version+1)) -} - -// Up runs a specific set of migrations. -func (m *SQLLiteMigrator) Up(ctx context.Context, migrations migrations.Migrations) error { - tx, err := m.db.Begin() - if err != nil { - return err - } - var commitErr error - defer func() { - if commitErr == nil { - return - } - logrus.WithError(commitErr).Warningf("tx commit failed") - if err := tx.Rollback(); err != nil { - logrus.WithError(err).Warningf("couldn't rollback after failed commit") - } - }() - - if err := m.ensureMigrationTable(ctx, tx); err != nil { - return err - } - - for _, migration := range migrations { - currentVersion, err := m.version(ctx, tx) - if err != nil { - return err - } - - if migration.Id != currentVersion+1 { - return fmt.Errorf("migration applied out of order") - } - - if err := migration.Up(ctx, tx); err != nil { - return err - } - - if err := m.setVersion(ctx, tx, migration.Id); err != nil { - return err - } - } - commitErr = tx.Commit() - return commitErr -} - -func (m *SQLLiteMigrator) Down(ctx context.Context, migrations migrations.Migrations) error { - tx, err := m.db.Begin() - if err != nil { - return err - } - var commitErr error - defer func() { - if commitErr == nil { - return - } - logrus.WithError(commitErr).Warningf("tx commit failed") - if err := tx.Rollback(); err != nil { - logrus.WithError(err).Warningf("couldn't rollback after failed commit") - } - }() - if err := m.ensureMigrationTable(ctx, tx); err != nil { - return err - } - - for _, migration := range migrations { - currentVersion, err := m.version(ctx, tx) - if err != nil { - return err - } - - if migration.Id != currentVersion { - return fmt.Errorf("migration applied out of order") - } - - if err := migration.Down(ctx, tx); err != nil { - return err - } - - if err := m.setVersion(ctx, tx, migration.Id-1); err != nil { - return err - } - } - commitErr = tx.Commit() - return commitErr -} - -func (m *SQLLiteMigrator) ensureMigrationTable(ctx context.Context, tx *sql.Tx) error { - sql := fmt.Sprintf(` - CREATE TABLE IF NOT EXISTS %s ( - version bigint NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP - ); - `, m.migrationsTable) - _, err := tx.ExecContext(ctx, sql) - return err -} - -func (m *SQLLiteMigrator) tableExists(tx *sql.Tx, table string) (bool, error) { - query := `SELECT count(*) - FROM sqlite_master - WHERE name = ?` - row := tx.QueryRow(query, table) - - var count int - err := row.Scan(&count) - if err != nil { - return false, err - } - - exists := count > 0 - return exists, nil -} - -func (m *SQLLiteMigrator) version(ctx context.Context, tx *sql.Tx) (int, error) { - tableExists, err := m.tableExists(tx, m.migrationsTable) - if err != nil { - return NilVersion, err - } - if !tableExists { - return NilVersion, nil - } - - query := `SELECT version FROM ` + m.migrationsTable + ` LIMIT 1` - var version int - err = tx.QueryRowContext(ctx, query).Scan(&version) - switch { - case errors.Is(err, sql.ErrNoRows): - return NilVersion, nil - case err != nil: - return NilVersion, err - default: - return version, nil - } -} - -func (m *SQLLiteMigrator) setVersion(ctx context.Context, tx *sql.Tx, version int) error { - if err := m.ensureMigrationTable(ctx, tx); err != nil { - return err - } - // nolint: gosec - _, err := tx.ExecContext(ctx, "DELETE FROM "+m.migrationsTable) - if err != nil { - return err - } - // nolint: gosec - _, err = tx.ExecContext(ctx, "INSERT INTO "+m.migrationsTable+"(version) values(?)", version) - return err -} diff --git a/pkg/sqlite/migrator_test.go b/pkg/sqlite/migrator_test.go deleted file mode 100644 index 8fd1aee2e..000000000 --- a/pkg/sqlite/migrator_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "fmt" - "reflect" - "sort" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/sqlite/migrations" -) - -func TestNewSQLLiteMigrator(t *testing.T) { - type args struct { - db *sql.DB - } - tests := []struct { - name string - args args - want Migrator - wantErr bool - }{ - { - name: "uses default table", - args: args{&sql.DB{}}, - want: &SQLLiteMigrator{db: &sql.DB{}, migrationsTable: DefaultMigrationsTable, migrations: migrations.All()}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewSQLLiteMigrator(tt.args.db) - if (err != nil) != tt.wantErr { - t.Errorf("NewSQLLiteMigrator() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewSQLLiteMigrator() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSQLLiteMigrator_Down(t *testing.T) { - var up bool - var down bool - - type fields struct { - migrationsTable string - } - type args struct { - ctx context.Context - migrations []*migrations.Migration - } - tests := []struct { - name string - fields fields - args args - wantErr bool - wantUp bool - wantDown bool - wantVersion int - }{ - { - name: "run test migration", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: []*migrations.Migration{{ - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - up = true - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }}}, - wantUp: false, - wantDown: true, - wantVersion: -1, - }, - { - name: "run migration out of order", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: []*migrations.Migration{{ - Id: 1, - Up: func(ctx context.Context, tx *sql.Tx) error { - up = true - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }}}, - wantUp: false, - wantDown: false, - wantErr: true, - wantVersion: 0, - }, - { - name: "run error migration", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: []*migrations.Migration{{ - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - return fmt.Errorf("error") - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - return fmt.Errorf("error") - }, - }}}, - wantErr: true, - wantUp: false, - wantDown: false, - wantVersion: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - up = false - down = false - db, cleanup := CreateTestDB(t) - defer cleanup() - m := &SQLLiteMigrator{ - db: db, - migrationsTable: tt.fields.migrationsTable, - } - { - tx, err := db.Begin() - require.NoError(t, err) - require.NoError(t, m.setVersion(context.TODO(), tx, 0)) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - // TODO: this shouldn't be unconditionally rolled back - // run_test_migration, run_migration_out_of_order, and run_error_migration each have at least one scenario - // where rollback is no longer possible (committed or rolled back already) - // In the interest of retaining function and a good lint bright line, we'll just ignore the error here - _ = tx.Rollback() - } - if err := m.Down(tt.args.ctx, tt.args.migrations); (err != nil) != tt.wantErr { - t.Errorf("Down() error = %v, wantErr %v", err, tt.wantErr) - } - require.Equal(t, tt.wantUp, up) - require.Equal(t, tt.wantDown, down) - - // verify the version is correct - var version int - { - tx, err := db.Begin() - require.NoError(t, err) - version, err = m.version(context.TODO(), tx) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - require.Equal(t, tt.wantVersion, version) - }) - } -} - -func TestSQLLiteMigrator_Up(t *testing.T) { - var up int - var down bool - - type fields struct { - migrationsTable string - } - type args struct { - ctx context.Context - migrations migrations.Migrations - } - tests := []struct { - name string - fields fields - args args - wantErr bool - wantUp int - wantDown bool - wantVersion int - }{ - { - name: "run test migration", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: migrations.Migrations{{ - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }}}, - wantUp: 1, - wantDown: false, - wantVersion: 0, - }, - { - name: "run multiple test migration", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: migrations.Migrations{ - { - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }, - { - Id: 1, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }, - }}, - wantUp: 2, - wantDown: false, - wantVersion: 1, - }, - { - name: "run migrations out of order", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: migrations.Migrations{ - { - Id: 1, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }, - { - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down = true - return nil - }, - }, - }}, - wantUp: 0, - wantDown: false, - wantErr: true, - wantVersion: -1, - }, - { - name: "run error migration", - fields: fields{migrationsTable: DefaultMigrationsTable}, - args: args{ctx: context.TODO(), migrations: migrations.Migrations{{ - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - return fmt.Errorf("error") - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - return fmt.Errorf("error") - }, - }}}, - wantErr: true, - wantUp: 0, - wantDown: false, - wantVersion: -1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - up = 0 - down = false - db, cleanup := CreateTestDB(t) - defer cleanup() - m := &SQLLiteMigrator{ - db: db, - migrationsTable: tt.fields.migrationsTable, - } - { - tx, err := db.Begin() - require.NoError(t, err) - require.NoError(t, m.setVersion(context.TODO(), tx, -1)) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - if err := m.Up(tt.args.ctx, tt.args.migrations); (err != nil) != tt.wantErr { - t.Errorf("Up() error = %v, wantErr %v", err, tt.wantErr) - } - require.Equal(t, tt.wantUp, up) - require.Equal(t, tt.wantDown, down) - - // verify the version is correct - var version int - { - tx, err := db.Begin() - require.NoError(t, err) - version, err = m.version(context.TODO(), tx) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - require.Equal(t, tt.wantVersion, version) - - if tt.wantErr { - return - } - - // walk backwards back to zero - sort.Sort(sort.Reverse(tt.args.migrations)) - err := m.Down(tt.args.ctx, tt.args.migrations) - require.NoError(t, err) - { - tx, err := db.Begin() - require.NoError(t, err) - version, err = m.version(context.TODO(), tx) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - require.Equal(t, NilVersion, version) - }) - } -} - -func TestSQLLiteMigrator_Migrate(t *testing.T) { - var up int - var down int - - migs := migrations.MigrationSet{ - 0: { - Id: 0, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down++ - return nil - }, - }, - 1: { - Id: 1, - Up: func(ctx context.Context, tx *sql.Tx) error { - up++ - return nil - }, - Down: func(ctx context.Context, tx *sql.Tx) error { - down++ - return nil - }, - }, - } - - tests := []struct { - name string - startVersion int - wantErr bool - wantUp int - wantDown int - wantVersion int - }{ - { - name: "up from nothing", - startVersion: -1, - wantUp: 2, - wantDown: 0, - wantVersion: 1, - }, - { - name: "up from initial db", - startVersion: 0, - wantUp: 1, - wantDown: 0, - wantVersion: 1, - }, - { - name: "at latest", - startVersion: 1, - wantUp: 0, - wantDown: 0, - wantVersion: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - up = 0 - down = 0 - db, cleanup := CreateTestDB(t) - defer cleanup() - m := &SQLLiteMigrator{ - db: db, - migrationsTable: DefaultMigrationsTable, - migrations: migs, - } - { - tx, err := db.Begin() - require.NoError(t, err) - require.NoError(t, m.setVersion(context.TODO(), tx, tt.startVersion)) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - if err := m.Migrate(context.TODO()); (err != nil) != tt.wantErr { - t.Errorf("Migrate() error = %v, wantErr %v", err, tt.wantErr) - } - require.Equal(t, tt.wantUp, up) - require.Equal(t, tt.wantDown, down) - - // verify the version is correct - var version int - { - tx, err := db.Begin() - require.NoError(t, err) - version, err = m.version(context.TODO(), tx) - require.NoError(t, err) - require.NoError(t, tx.Commit()) - _ = tx.Rollback() - } - require.Equal(t, tt.wantVersion, version) - }) - } -} diff --git a/pkg/sqlite/query.go b/pkg/sqlite/query.go deleted file mode 100644 index d6024d551..000000000 --- a/pkg/sqlite/query.go +++ /dev/null @@ -1,1404 +0,0 @@ -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o sqlitefakes/fake_rowscanner.go . RowScanner -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o sqlitefakes/fake_querier.go . Querier -package sqlite - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" - - _ "github.com/mattn/go-sqlite3" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type RowScanner interface { - Next() bool - Close() error - Scan(dest ...interface{}) error -} - -type Querier interface { - QueryContext(ctx context.Context, query string, args ...interface{}) (RowScanner, error) -} - -type dbQuerierAdapter struct { - db *sql.DB -} - -func (a dbQuerierAdapter) QueryContext(ctx context.Context, query string, args ...interface{}) (RowScanner, error) { - return a.db.QueryContext(ctx, query, args...) -} - -type SQLQuerier struct { - db Querier - querierConfig -} - -var _ registry.Query = &SQLQuerier{} - -type querierConfig struct { - omitManifests bool -} - -type SQLiteQuerierOption func(*querierConfig) - -// If true, ListBundles will omit inline manifests (the object and -// csvJson fields) from response elements that contain a bundle image -// reference. -func OmitManifests(b bool) SQLiteQuerierOption { - return func(c *querierConfig) { - c.omitManifests = b - } -} - -func NewSQLLiteQuerier(dbFilename string, opts ...SQLiteQuerierOption) (*SQLQuerier, error) { - db, err := OpenReadOnly(dbFilename) - if err != nil { - return nil, err - } - return NewSQLLiteQuerierFromDb(db, opts...), nil -} - -func NewSQLLiteQuerierFromDb(db *sql.DB, opts ...SQLiteQuerierOption) *SQLQuerier { - return NewSQLLiteQuerierFromDBQuerier(dbQuerierAdapter{db}, opts...) -} - -func NewSQLLiteQuerierFromDBQuerier(q Querier, opts ...SQLiteQuerierOption) *SQLQuerier { - sq := SQLQuerier{db: q} - for _, opt := range opts { - opt(&sq.querierConfig) - } - return &sq -} - -func (s *SQLQuerier) ListTables(ctx context.Context) ([]string, error) { - query := "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" - rows, err := s.db.QueryContext(ctx, query) - if err != nil { - return nil, err - } - defer rows.Close() - - tables := []string{} - for rows.Next() { - var tableName sql.NullString - if err := rows.Scan(&tableName); err != nil { - return nil, err - } - if tableName.Valid { - tables = append(tables, tableName.String) - } - } - return tables, nil -} - -// ListPackages returns a list of package names as strings -func (s *SQLQuerier) ListPackages(ctx context.Context) ([]string, error) { - query := "SELECT DISTINCT name FROM package" - rows, err := s.db.QueryContext(ctx, query) - if err != nil { - return nil, err - } - defer rows.Close() - - packages := []string{} - for rows.Next() { - var pkgName sql.NullString - if err := rows.Scan(&pkgName); err != nil { - return nil, err - } - if pkgName.Valid { - packages = append(packages, pkgName.String) - } - } - return packages, nil -} - -func (s *SQLQuerier) GetPackage(ctx context.Context, name string) (*registry.PackageManifest, error) { - query := `SELECT DISTINCT package.name, default_channel, channel.name, channel.head_operatorbundle_name - FROM package INNER JOIN channel ON channel.package_name=package.name - WHERE package.name=?` - rows, err := s.db.QueryContext(ctx, query, name) - if err != nil { - return nil, err - } - defer rows.Close() - - var pkgName sql.NullString - var defaultChannel sql.NullString - var channelName sql.NullString - var bundleName sql.NullString - if !rows.Next() { - return nil, fmt.Errorf("package %s not found", name) - } - if err := rows.Scan(&pkgName, &defaultChannel, &channelName, &bundleName); err != nil { - return nil, err - } - pkg := ®istry.PackageManifest{ - PackageName: pkgName.String, - DefaultChannelName: defaultChannel.String, - Channels: []registry.PackageChannel{ - { - Name: channelName.String, - CurrentCSVName: bundleName.String, - }, - }, - } - - for rows.Next() { - if err := rows.Scan(&pkgName, &defaultChannel, &channelName, &bundleName); err != nil { - return nil, err - } - pkg.Channels = append(pkg.Channels, registry.PackageChannel{Name: channelName.String, CurrentCSVName: bundleName.String}) - } - return pkg, nil -} - -func (s *SQLQuerier) GetDefaultPackage(ctx context.Context, name string) (string, error) { - query := `SELECT default_channel - FROM package WHERE package.name=?` - rows, err := s.db.QueryContext(ctx, query, name) - if err != nil { - return "", err - } - defer rows.Close() - - var defaultChannel sql.NullString - if !rows.Next() { - return "", fmt.Errorf("package %s not found", name) - } - if err := rows.Scan(&defaultChannel); err != nil { - return "", err - } - - if !defaultChannel.Valid { - return "", fmt.Errorf("default channel not valid") - } - - return defaultChannel.String, nil -} - -func (s *SQLQuerier) GetChannelEntriesFromPackage(ctx context.Context, packageName string) ([]registry.ChannelEntryAnnotated, error) { - query := `SELECT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name, op_bundle.version, op_bundle.bundlepath, replaces.operatorbundle_name, replacesbundle.version, replacesbundle.bundlepath - FROM channel_entry - LEFT JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - LEFT JOIN operatorbundle op_bundle ON channel_entry.operatorbundle_name = op_bundle.name - LEFT JOIN operatorbundle replacesbundle ON replaces.operatorbundle_name = replacesbundle.name - WHERE channel_entry.package_name = ?;` - - var entries []registry.ChannelEntryAnnotated - rows, err := s.db.QueryContext(ctx, query, packageName) - if err != nil { - return nil, err - } - defer rows.Close() - - var pkgName sql.NullString - var channelName sql.NullString - var bundleName sql.NullString - var replaces sql.NullString - var version sql.NullString - var bundlePath sql.NullString - var replacesVersion sql.NullString - var replacesBundlePath sql.NullString - - for rows.Next() { - if err := rows.Scan(&pkgName, &channelName, &bundleName, &version, &bundlePath, &replaces, &replacesVersion, &replacesBundlePath); err != nil { - return nil, err - } - - channelEntryNode := registry.ChannelEntryAnnotated{ - PackageName: pkgName.String, - ChannelName: channelName.String, - BundleName: bundleName.String, - Version: version.String, - BundlePath: bundlePath.String, - Replaces: replaces.String, - ReplacesVersion: replacesVersion.String, - ReplacesBundlePath: replacesBundlePath.String, - } - - entries = append(entries, channelEntryNode) - } - - return entries, nil -} - -func (s *SQLQuerier) GetBundle(ctx context.Context, pkgName, channelName, csvName string) (*api.Bundle, error) { - query := `SELECT DISTINCT channel_entry.entry_id, operatorbundle.name, operatorbundle.bundle, operatorbundle.bundlepath, operatorbundle.version, operatorbundle.skiprange - FROM operatorbundle INNER JOIN channel_entry ON operatorbundle.name=channel_entry.operatorbundle_name - WHERE channel_entry.package_name=? AND channel_entry.channel_name=? AND operatorbundle_name=? LIMIT 1` - rows, err := s.db.QueryContext(ctx, query, pkgName, channelName, csvName) - if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { - return nil, fmt.Errorf("no entry found for %s %s %s", pkgName, channelName, csvName) - } - var entryID sql.NullInt64 - var name sql.NullString - var bundle sql.NullString - var bundlePath sql.NullString - var version sql.NullString - var skipRange sql.NullString - if err := rows.Scan(&entryID, &name, &bundle, &bundlePath, &version, &skipRange); err != nil { - return nil, err - } - - out := &api.Bundle{} - if bundle.Valid && bundle.String != "" { - out, err = registry.BundleStringToAPIBundle(bundle.String) - if err != nil { - return nil, err - } - } - out.CsvName = name.String - out.PackageName = pkgName - out.ChannelName = channelName - out.BundlePath = bundlePath.String - out.Version = version.String - out.SkipRange = skipRange.String - - provided, required, err := s.GetApisForEntry(ctx, entryID.Int64) - if err != nil { - return nil, err - } - out.ProvidedApis = provided - out.RequiredApis = required - - dependencies, err := s.GetDependenciesForBundle(ctx, name.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Dependencies = dependencies - - properties, err := s.GetPropertiesForBundle(ctx, name.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Properties = properties - - return out, nil -} - -func (s *SQLQuerier) GetBundleForChannel(ctx context.Context, pkg string, channel string) (*api.Bundle, error) { - query := ` -SELECT operatorbundle.name, operatorbundle.csv FROM operatorbundle INNER JOIN channel -ON channel.head_operatorbundle_name = operatorbundle.name -WHERE channel.name = :channel AND channel.package_name = :package` - rows, err := s.db.QueryContext(ctx, query, sql.Named("channel", channel), sql.Named("package", pkg)) - if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { - return nil, fmt.Errorf("no entry found for %s %s", pkg, channel) - } - var ( - name sql.NullString - csv sql.NullString - ) - if err := rows.Scan(&name, &csv); err != nil { - return nil, err - } - - return &api.Bundle{ - CsvName: name.String, - CsvJson: csv.String, - }, nil -} - -func (s *SQLQuerier) GetChannelEntriesThatReplace(ctx context.Context, name string) ([]*registry.ChannelEntry, error) { - query := `SELECT DISTINCT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name - FROM channel_entry - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE replaces.operatorbundle_name = ?` - rows, err := s.db.QueryContext(ctx, query, name) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries []*registry.ChannelEntry - - for rows.Next() { - var pkgNameSQL sql.NullString - var channelNameSQL sql.NullString - var bundleNameSQL sql.NullString - - if err = rows.Scan(&pkgNameSQL, &channelNameSQL, &bundleNameSQL); err != nil { - return nil, err - } - entries = append(entries, ®istry.ChannelEntry{ - PackageName: pkgNameSQL.String, - ChannelName: channelNameSQL.String, - BundleName: bundleNameSQL.String, - Replaces: name, - }) - } - if len(entries) == 0 { - err = fmt.Errorf("no channel entries found that replace %s", name) - return nil, err - } - return entries, nil -} - -func (s *SQLQuerier) GetBundleThatReplaces(ctx context.Context, name, pkgName, channelName string) (*api.Bundle, error) { - query := `SELECT DISTINCT replaces.entry_id, operatorbundle.name, operatorbundle.bundle, operatorbundle.bundlepath, operatorbundle.version, operatorbundle.skiprange - FROM channel_entry - LEFT OUTER JOIN channel_entry replaces ON replaces.replaces = channel_entry.entry_id - INNER JOIN operatorbundle ON replaces.operatorbundle_name = operatorbundle.name - WHERE channel_entry.operatorbundle_name = ? AND channel_entry.package_name = ? AND channel_entry.channel_name = ? LIMIT 1` - rows, err := s.db.QueryContext(ctx, query, name, pkgName, channelName) - if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { - return nil, fmt.Errorf("no entry found for %s %s", pkgName, channelName) - } - var entryID sql.NullInt64 - var outName sql.NullString - var bundle sql.NullString - var bundlePath sql.NullString - var version sql.NullString - var skipRange sql.NullString - if err := rows.Scan(&entryID, &outName, &bundle, &bundlePath, &version, &skipRange); err != nil { - return nil, err - } - - out := &api.Bundle{} - if bundle.Valid && bundle.String != "" { - out, err = registry.BundleStringToAPIBundle(bundle.String) - if err != nil { - return nil, err - } - } - out.CsvName = outName.String - out.PackageName = pkgName - out.ChannelName = channelName - out.BundlePath = bundlePath.String - out.Version = version.String - out.SkipRange = skipRange.String - - provided, required, err := s.GetApisForEntry(ctx, entryID.Int64) - if err != nil { - return nil, err - } - out.ProvidedApis = provided - out.RequiredApis = required - - dependencies, err := s.GetDependenciesForBundle(ctx, outName.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Dependencies = dependencies - - properties, err := s.GetPropertiesForBundle(ctx, outName.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Properties = properties - - return out, nil -} - -func (s *SQLQuerier) GetChannelEntriesThatProvide(ctx context.Context, group, version, kind string) ([]*registry.ChannelEntry, error) { - // TODO: join on full fk, not just operatorbundlename - query := `SELECT DISTINCT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name, replaces.operatorbundle_name - FROM channel_entry - INNER JOIN properties ON channel_entry.operatorbundle_name = properties.operatorbundle_name - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE properties.type=? AND properties.value=?` - - value, err := json.Marshal(map[string]string{ - "group": group, - "version": version, - "kind": kind, - }) - if err != nil { - return nil, err - } - rows, err := s.db.QueryContext(ctx, query, registry.GVKType, string(value)) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries []*registry.ChannelEntry - - for rows.Next() { - var pkgNameSQL sql.NullString - var channelNameSQL sql.NullString - var bundleNameSQL sql.NullString - var replacesSQL sql.NullString - if err = rows.Scan(&pkgNameSQL, &channelNameSQL, &bundleNameSQL, &replacesSQL); err != nil { - return nil, err - } - - entries = append(entries, ®istry.ChannelEntry{ - PackageName: pkgNameSQL.String, - ChannelName: channelNameSQL.String, - BundleName: bundleNameSQL.String, - Replaces: replacesSQL.String, - }) - } - if len(entries) == 0 { - err = fmt.Errorf("no channel entries found that provide %s %s %s", group, version, kind) - return nil, err - } - return entries, nil -} - -// Get latest channel entries that provide an api -func (s *SQLQuerier) GetLatestChannelEntriesThatProvide(ctx context.Context, group, version, kind string) ([]*registry.ChannelEntry, error) { - query := `SELECT DISTINCT channel_entry.package_name, channel_entry.channel_name, channel_entry.operatorbundle_name, replaces.operatorbundle_name, MIN(channel_entry.depth) - FROM channel_entry - INNER JOIN properties ON channel_entry.operatorbundle_name = properties.operatorbundle_name - LEFT OUTER JOIN channel_entry replaces ON channel_entry.replaces = replaces.entry_id - WHERE properties.type = ? AND properties.value = ? - GROUP BY channel_entry.package_name, channel_entry.channel_name` - - value, err := json.Marshal(map[string]string{ - "group": group, - "version": version, - "kind": kind, - }) - if err != nil { - return nil, err - } - - rows, err := s.db.QueryContext(ctx, query, registry.GVKType, string(value)) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries []*registry.ChannelEntry - - for rows.Next() { - var pkgNameSQL sql.NullString - var channelNameSQL sql.NullString - var bundleNameSQL sql.NullString - var replacesSQL sql.NullString - var minDepth sql.NullInt64 - if err = rows.Scan(&pkgNameSQL, &channelNameSQL, &bundleNameSQL, &replacesSQL, &minDepth); err != nil { - return nil, err - } - - entries = append(entries, ®istry.ChannelEntry{ - PackageName: pkgNameSQL.String, - ChannelName: channelNameSQL.String, - BundleName: bundleNameSQL.String, - Replaces: replacesSQL.String, - }) - } - if len(entries) == 0 { - err = fmt.Errorf("no channel entries found that provide %s %s %s", group, version, kind) - return nil, err - } - return entries, nil -} - -// Get the the latest bundle that provides the API in a default channel, error unless there is ONLY one -func (s *SQLQuerier) GetBundleThatProvides(ctx context.Context, group, apiVersion, kind string) (*api.Bundle, error) { - query := `SELECT DISTINCT channel_entry.entry_id, operatorbundle.bundle, operatorbundle.bundlepath, MIN(channel_entry.depth), channel_entry.operatorbundle_name, channel_entry.package_name, channel_entry.channel_name, channel_entry.replaces, operatorbundle.version, operatorbundle.skiprange - FROM channel_entry - INNER JOIN operatorbundle ON operatorbundle.name = channel_entry.operatorbundle_name - INNER JOIN properties ON channel_entry.operatorbundle_name = properties.operatorbundle_name - INNER JOIN package ON package.name = channel_entry.package_name - WHERE properties.type = ? AND properties.value = ? AND package.default_channel = channel_entry.channel_name - GROUP BY channel_entry.package_name, channel_entry.channel_name` - - value, _ := json.Marshal(map[string]string{ - "group": group, - "version": apiVersion, - "kind": kind, - }) - rows, err := s.db.QueryContext(ctx, query, registry.GVKType, string(value)) - if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { - return nil, fmt.Errorf("no entry found that provides %s %s %s", group, apiVersion, kind) - } - var entryID sql.NullInt64 - var bundle sql.NullString - var bundlePath sql.NullString - var minDepth sql.NullInt64 - var bundleName sql.NullString - var pkgName sql.NullString - var channelName sql.NullString - var replaces sql.NullString - var version sql.NullString - var skipRange sql.NullString - if err := rows.Scan(&entryID, &bundle, &bundlePath, &minDepth, &bundleName, &pkgName, &channelName, &replaces, &version, &skipRange); err != nil { - return nil, err - } - - if !bundle.Valid { - return nil, fmt.Errorf("no entry found that provides %s %s %s", group, apiVersion, kind) - } - - out := &api.Bundle{} - if bundle.Valid && bundle.String != "" { - out, err = registry.BundleStringToAPIBundle(bundle.String) - if err != nil { - return nil, err - } - } - out.CsvName = bundleName.String - out.PackageName = pkgName.String - out.ChannelName = channelName.String - out.BundlePath = bundlePath.String - out.Version = version.String - out.SkipRange = skipRange.String - - provided, required, err := s.GetApisForEntry(ctx, entryID.Int64) - if err != nil { - return nil, err - } - out.ProvidedApis = provided - out.RequiredApis = required - - dependencies, err := s.GetDependenciesForBundle(ctx, bundleName.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Dependencies = dependencies - - properties, err := s.GetPropertiesForBundle(ctx, bundleName.String, version.String, bundlePath.String) - if err != nil { - return nil, err - } - out.Properties = properties - - return out, nil -} - -func (s *SQLQuerier) ListImages(ctx context.Context) ([]string, error) { - query := "SELECT DISTINCT image FROM related_image" - rows, err := s.db.QueryContext(ctx, query) - if err != nil { - return nil, err - } - defer rows.Close() - - images := []string{} - for rows.Next() { - var imgName sql.NullString - if err := rows.Scan(&imgName); err != nil { - return nil, err - } - if imgName.Valid { - images = append(images, imgName.String) - } - } - return images, nil -} - -func (s *SQLQuerier) GetImagesForBundle(ctx context.Context, csvName string) ([]string, error) { - query := "SELECT DISTINCT image FROM related_image WHERE operatorbundle_name=?" - rows, err := s.db.QueryContext(ctx, query, csvName) - if err != nil { - return nil, err - } - defer rows.Close() - images := []string{} - for rows.Next() { - var imgName sql.NullString - if err := rows.Scan(&imgName); err != nil { - return nil, err - } - if imgName.Valid { - images = append(images, imgName.String) - } - } - return images, nil -} - -func (s *SQLQuerier) GetApisForEntry(ctx context.Context, entryID int64) ([]*api.GroupVersionKind, []*api.GroupVersionKind, error) { - groups := map[string]struct{}{} - kinds := map[string]struct{}{} - versions := map[string]struct{}{} - - providedQuery := `SELECT properties.value FROM properties - INNER JOIN channel_entry ON channel_entry.operatorbundle_name = properties.operatorbundle_name - WHERE properties.type=? AND channel_entry.entry_id=?` - - providedRows, err := s.db.QueryContext(ctx, providedQuery, registry.GVKType, entryID) - if err != nil { - return nil, nil, err - } - defer providedRows.Close() - - var provided []*api.GroupVersionKind - for providedRows.Next() { - var value sql.NullString - - if err := providedRows.Scan(&value); err != nil { - return nil, nil, err - } - - if !value.Valid { - continue - } - prop := registry.GVKProperty{} - if err := json.Unmarshal([]byte(value.String), &prop); err != nil { - continue - } - - provided = append(provided, &api.GroupVersionKind{ - Group: prop.Group, - Version: prop.Version, - Kind: prop.Kind, - }) - groups[prop.Group] = struct{}{} - versions[prop.Version] = struct{}{} - kinds[prop.Kind] = struct{}{} - } - - requiredQuery := `SELECT DISTINCT dependencies.value FROM dependencies - INNER JOIN channel_entry ON channel_entry.operatorbundle_name = dependencies.operatorbundle_name - WHERE dependencies.type=? AND channel_entry.entry_id=?` - - requiredRows, err := s.db.QueryContext(ctx, requiredQuery, registry.GVKType, entryID) - if err != nil { - return nil, nil, err - } - defer requiredRows.Close() - - var required []*api.GroupVersionKind - for requiredRows.Next() { - var value sql.NullString - - if err := requiredRows.Scan(&value); err != nil { - return nil, nil, err - } - if !value.Valid { - continue - } - dep := registry.GVKDependency{} - if err := json.Unmarshal([]byte(value.String), &dep); err != nil { - continue - } - - required = append(required, &api.GroupVersionKind{ - Group: dep.Group, - Version: dep.Version, - Kind: dep.Kind, - }) - groups[dep.Group] = struct{}{} - versions[dep.Version] = struct{}{} - kinds[dep.Kind] = struct{}{} - } - - argsFor := func(s map[string]struct{}) string { - l := make([]string, 0, len(s)) - for v := range s { - l = append(l, "\""+v+"\"") - } - return "(" + strings.Join(l, ",") + ")" - } - - pluralQuery := `SELECT * FROM api` + - ` WHERE api.group_name IN ` + argsFor(groups) + - ` AND api.version IN ` + argsFor(versions) + - ` AND api.kind IN ` + argsFor(kinds) - - pluralRows, err := s.db.QueryContext(ctx, pluralQuery) - if err != nil { - return nil, nil, err - } - defer pluralRows.Close() - - gvkToPlural := map[registry.GVKProperty]string{} - for pluralRows.Next() { - var groupName sql.NullString - var versionName sql.NullString - var kindName sql.NullString - var pluralName sql.NullString - - if err := pluralRows.Scan(&groupName, &versionName, &kindName, &pluralName); err != nil { - continue - } - if !groupName.Valid || !versionName.Valid || !kindName.Valid || !pluralName.Valid { - continue - } - gvkToPlural[registry.GVKProperty{ - Group: groupName.String, - Version: versionName.String, - Kind: kindName.String, - }] = pluralName.String - } - - for i, p := range provided { - if p.Plural != "" { - continue - } - plural, ok := gvkToPlural[registry.GVKProperty{ - Group: p.Group, - Version: p.Version, - Kind: p.Kind, - }] - if !ok { - continue - } - provided[i].Plural = plural - } - for i, r := range required { - if r.Plural != "" { - continue - } - plural, ok := gvkToPlural[registry.GVKProperty{ - Group: r.Group, - Version: r.Version, - Kind: r.Kind, - }] - if !ok { - continue - } - required[i].Plural = plural - } - return provided, required, nil -} - -func (s *SQLQuerier) GetBundleVersion(ctx context.Context, image string) (string, error) { - query := `SELECT version FROM operatorbundle WHERE bundlepath=? LIMIT 1` - rows, err := s.db.QueryContext(ctx, query, image) - if err != nil { - return "", err - } - defer rows.Close() - - var version sql.NullString - if rows.Next() { - if err := rows.Scan(&version); err != nil { - return "", err - } - } - if version.Valid { - return version.String, nil - } - return "", nil -} - -func (s *SQLQuerier) GetBundlePathsForPackage(ctx context.Context, pkgName string) ([]string, error) { - query := `SELECT DISTINCT bundlepath FROM operatorbundle - INNER JOIN channel_entry ON operatorbundle.name=channel_entry.operatorbundle_name - WHERE channel_entry.package_name=?` - rows, err := s.db.QueryContext(ctx, query, pkgName) - if err != nil { - return nil, err - } - defer rows.Close() - images := []string{} - for rows.Next() { - var imgName sql.NullString - if err := rows.Scan(&imgName); err != nil { - return nil, err - } - if imgName.Valid && imgName.String == "" { - //nolint:staticcheck // ST1005: error message is intentionally capitalized - return nil, fmt.Errorf("Index malformed: cannot find paths to bundle images") - } - images = append(images, imgName.String) - } - return images, nil -} - -func (s *SQLQuerier) GetBundlesForPackage(ctx context.Context, pkgName string) (map[registry.BundleKey]struct{}, error) { - query := `SELECT DISTINCT name, bundlepath, version FROM operatorbundle - INNER JOIN channel_entry ON operatorbundle.name=channel_entry.operatorbundle_name - WHERE channel_entry.package_name=?` - rows, err := s.db.QueryContext(ctx, query, pkgName) - if err != nil { - return nil, err - } - defer rows.Close() - bundles := map[registry.BundleKey]struct{}{} - for rows.Next() { - var name sql.NullString - var bundlepath sql.NullString - var version sql.NullString - if err := rows.Scan(&name, &bundlepath, &version); err != nil { - return nil, err - } - key := registry.BundleKey{} - if name.Valid && name.String != "" { - key.CsvName = name.String - } - if bundlepath.Valid && bundlepath.String != "" { - key.BundlePath = bundlepath.String - } - if version.Valid && version.String != "" { - key.Version = version.String - } - if key.IsEmpty() { - //nolint:staticcheck // ST1005: error message is intentionally capitalized - return nil, fmt.Errorf("Index malformed: cannot find identifier for bundle in package %s", pkgName) - } - bundles[key] = struct{}{} - } - return bundles, nil -} - -func (s *SQLQuerier) GetDefaultChannelForPackage(ctx context.Context, pkgName string) (string, error) { - query := `SELECT DISTINCT default_channel FROM package WHERE name=? LIMIT 1` - rows, err := s.db.QueryContext(ctx, query, pkgName) - if err != nil { - return "", err - } - defer rows.Close() - - var defaultChannel sql.NullString - if rows.Next() { - if err := rows.Scan(&defaultChannel); err != nil { - return "", err - } - } - if defaultChannel.Valid { - return defaultChannel.String, nil - } - return "", nil -} - -func (s *SQLQuerier) ListChannels(ctx context.Context, pkgName string) ([]string, error) { - query := `SELECT DISTINCT name FROM channel WHERE channel.package_name=?` - rows, err := s.db.QueryContext(ctx, query, pkgName) - if err != nil { - return nil, err - } - defer rows.Close() - channels := []string{} - for rows.Next() { - var chName sql.NullString - if err := rows.Scan(&chName); err != nil { - return nil, err - } - if chName.Valid { - channels = append(channels, chName.String) - } - } - return channels, nil -} - -func (s *SQLQuerier) GetCurrentCSVNameForChannel(ctx context.Context, pkgName, channel string) (string, error) { - query := `SELECT DISTINCT head_operatorbundle_name FROM channel WHERE channel.package_name=? AND channel.name=?` - rows, err := s.db.QueryContext(ctx, query, pkgName, channel) - if err != nil { - return "", err - } - defer rows.Close() - var csvName sql.NullString - if rows.Next() { - if err := rows.Scan(&csvName); err != nil { - return "", err - } - } - if csvName.Valid { - return csvName.String, nil - } - return "", nil -} - -// Rows in the channel_entry table essentially represent inbound -// upgrade edges to a bundle. There may be no linear "replaces" chain -// (for example, when an index is populated using semver-skippatch -// mode), and there may be multiple inbound "skips" to a single -// bundle. The ListBundles query determines a single "replaces" value -// per bundle per channel by recursively following "replaces" -// references beginning from the entries with minimal depth, which -// represent channel heads. All other edges are merged into an -// aggregate "skips" column. The result contains one row per bundle -// for each channel in which the bundle appears. -const listBundlesQuery = ` -WITH RECURSIVE -tip (depth) AS ( - SELECT min(depth) - FROM channel_entry -), replaces_entry (entry_id, replaces) AS ( - SELECT entry_id, replaces - FROM channel_entry - INNER JOIN tip ON channel_entry.depth = tip.depth - UNION - SELECT channel_entry.entry_id, channel_entry.replaces - FROM channel_entry - INNER JOIN replaces_entry - ON channel_entry.entry_id = replaces_entry.replaces -), replaces_bundle (entry_id, operatorbundle_name, package_name, channel_name, replaces) AS ( - SELECT min(all_entry.entry_id), all_entry.operatorbundle_name, all_entry.package_name, all_entry.channel_name, max(replaced_entry.operatorbundle_name) - FROM channel_entry AS all_entry - LEFT OUTER JOIN replaces_entry - ON all_entry.entry_id = replaces_entry.entry_id - LEFT OUTER JOIN channel_entry AS replaced_entry - ON replaces_entry.replaces = replaced_entry.entry_id - GROUP BY all_entry.operatorbundle_name, all_entry.package_name, all_entry.channel_name -), skips_entry (entry_id, skips) AS ( - SELECT entry_id, replaces - FROM channel_entry - WHERE replaces IS NOT NULL - EXCEPT - SELECT entry_id, replaces - FROM replaces_entry -), skips_bundle (operatorbundle_name, package_name, channel_name, skips) AS ( - SELECT all_entry.operatorbundle_name, all_entry.package_name, all_entry.channel_name, group_concat(skipped_entry.operatorbundle_name, ",") - FROM skips_entry - INNER JOIN channel_entry AS all_entry - ON skips_entry.entry_id = all_entry.entry_id - INNER JOIN channel_entry AS skipped_entry - ON skips_entry.skips = skipped_entry.entry_id - GROUP BY all_entry.operatorbundle_name, all_entry.package_name, all_entry.channel_name -), -merged_properties (bundle_name, merged) AS ( - SELECT operatorbundle_name, json_group_array(json_object('type', CAST(properties.type AS TEXT), 'value', CAST(properties.value AS TEXT))) - FROM properties - GROUP BY operatorbundle_name -), -merged_dependencies (bundle_name, merged) AS ( - SELECT operatorbundle_name, json_group_array(json_object('type', CAST(dependencies.type AS TEXT), 'value', CAST(dependencies.value AS TEXT))) - FROM dependencies - GROUP BY operatorbundle_name -) -SELECT - replaces_bundle.entry_id, - CASE WHEN :omit_manifests AND length(coalesce(operatorbundle.bundlepath, "")) > 0 THEN NULL ELSE operatorbundle.bundle END, - operatorbundle.bundlepath, - operatorbundle.name, - replaces_bundle.package_name, - replaces_bundle.channel_name, - replaces_bundle.replaces, - skips_bundle.skips, - operatorbundle.version, - operatorbundle.skiprange, - merged_dependencies.merged, - merged_properties.merged - FROM replaces_bundle - INNER JOIN operatorbundle - ON replaces_bundle.operatorbundle_name = operatorbundle.name - LEFT OUTER JOIN skips_bundle - ON replaces_bundle.operatorbundle_name = skips_bundle.operatorbundle_name - AND replaces_bundle.package_name = skips_bundle.package_name - AND replaces_bundle.channel_name = skips_bundle.channel_name - LEFT OUTER JOIN merged_dependencies - ON operatorbundle.name = merged_dependencies.bundle_name - LEFT OUTER JOIN merged_properties - ON operatorbundle.name = merged_properties.bundle_name` - -func (s *SQLQuerier) SendBundles(ctx context.Context, stream registry.BundleSender) error { - rows, err := s.db.QueryContext(ctx, listBundlesQuery, sql.Named("omit_manifests", s.omitManifests)) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var ( - entryID sql.NullInt64 - bundle sql.NullString - bundlePath sql.NullString - bundleName sql.NullString - pkgName sql.NullString - channelName sql.NullString - replaces sql.NullString - skips sql.NullString - version sql.NullString - skipRange sql.NullString - deps sql.NullString - props sql.NullString - ) - if err := rows.Scan(&entryID, &bundle, &bundlePath, &bundleName, &pkgName, &channelName, &replaces, &skips, &version, &skipRange, &deps, &props); err != nil { - return err - } - - if !bundleName.Valid || !version.Valid || !bundlePath.Valid || !channelName.Valid { - continue - } - - out := &api.Bundle{} - if bundle.Valid && bundle.String != "" { - out, err = registry.BundleStringToAPIBundle(bundle.String) - if err != nil { - return err - } - } - out.CsvName = bundleName.String - out.PackageName = pkgName.String - out.ChannelName = channelName.String - out.BundlePath = bundlePath.String - out.Version = version.String - out.SkipRange = skipRange.String - out.Replaces = replaces.String - - if skips.Valid { - out.Skips = strings.Split(skips.String, ",") - } - - if deps.Valid { - if err := json.Unmarshal([]byte(deps.String), &out.Dependencies); err != nil { - return err - } - } - _ = buildLegacyRequiredAPIs(out.Dependencies, &out.RequiredApis) - out.Dependencies = uniqueDeps(out.Dependencies) - - if props.Valid { - if err := json.Unmarshal([]byte(props.String), &out.Properties); err != nil { - return err - } - } - _ = buildLegacyProvidedAPIs(out.Properties, &out.ProvidedApis) - out.Properties = uniqueProps(out.Properties) - if err := stream.Send(out); err != nil { - return err - } - } - - return nil -} - -type sliceBundleSender []*api.Bundle - -func (s *sliceBundleSender) Send(b *api.Bundle) error { - *s = append(*s, b) - return nil -} - -func (s *SQLQuerier) ListBundles(ctx context.Context) ([]*api.Bundle, error) { - var bundleSender sliceBundleSender - err := s.SendBundles(ctx, &bundleSender) - if err != nil { - return nil, err - } - return bundleSender, nil -} - -func buildLegacyRequiredAPIs(src []*api.Dependency, dst *[]*api.GroupVersionKind) error { - for _, p := range src { - if p.GetType() != registry.GVKType { - continue - } - var value registry.GVKDependency - if err := json.Unmarshal([]byte(p.GetValue()), &value); err != nil { - return err - } - *dst = append(*dst, &api.GroupVersionKind{ - Group: value.Group, - Version: value.Version, - Kind: value.Kind, - }) - } - return nil -} - -func buildLegacyProvidedAPIs(src []*api.Property, dst *[]*api.GroupVersionKind) error { - for _, p := range src { - if p.GetType() != registry.GVKType { - continue - } - var value registry.GVKProperty - if err := json.Unmarshal([]byte(p.GetValue()), &value); err != nil { - return err - } - *dst = append(*dst, &api.GroupVersionKind{ - Group: value.Group, - Version: value.Version, - Kind: value.Kind, - }) - } - return nil -} - -func uniqueDeps(deps []*api.Dependency) []*api.Dependency { - if len(deps) <= 1 { - return deps - } - keys := make(map[string]struct{}) - var list []*api.Dependency - for _, entry := range deps { - depKey := fmt.Sprintf("%s/%s", entry.Type, entry.Value) - if _, value := keys[depKey]; !value { - keys[depKey] = struct{}{} - list = append(list, entry) - } - } - return list -} - -func uniqueProps(props []*api.Property) []*api.Property { - if len(props) <= 1 { - return props - } - keys := make(map[string]struct{}) - var list []*api.Property - for _, entry := range props { - propKey := fmt.Sprintf("%s/%s", entry.Type, entry.Value) - if _, value := keys[propKey]; !value { - keys[propKey] = struct{}{} - list = append(list, entry) - } - } - return list -} - -func (s *SQLQuerier) GetDependenciesForBundle(ctx context.Context, name, version, path string) ([]*api.Dependency, error) { - depQuery := `SELECT DISTINCT type, value FROM dependencies - WHERE operatorbundle_name=? - AND (operatorbundle_version=? OR operatorbundle_version is NULL) - AND (operatorbundle_path=? OR operatorbundle_path is NULL)` - - rows, err := s.db.QueryContext(ctx, depQuery, name, version, path) - if err != nil { - return nil, err - } - defer rows.Close() - - var dependencies []*api.Dependency - for rows.Next() { - var typeName sql.NullString - var value sql.NullString - - if err := rows.Scan(&typeName, &value); err != nil { - return nil, err - } - if !typeName.Valid || !value.Valid { - return nil, err - } - dependencies = append(dependencies, &api.Dependency{ - Type: typeName.String, - Value: value.String, - }) - } - - return dependencies, nil -} - -func (s *SQLQuerier) GetPropertiesForBundle(ctx context.Context, name, version, path string) ([]*api.Property, error) { - propQuery := `SELECT DISTINCT type, value FROM properties - WHERE operatorbundle_name=? - AND (operatorbundle_version=? OR operatorbundle_version is NULL) - AND (operatorbundle_path=? OR operatorbundle_path is NULL)` - - rows, err := s.db.QueryContext(ctx, propQuery, name, version, path) - if err != nil { - return nil, err - } - defer rows.Close() - - var properties []*api.Property - for rows.Next() { - var typeName sql.NullString - var value sql.NullString - - if err := rows.Scan(&typeName, &value); err != nil { - return nil, err - } - if !typeName.Valid || !value.Valid { - return nil, err - } - properties = append(properties, &api.Property{ - Type: typeName.String, - Value: value.String, - }) - } - - return properties, nil -} - -func (s *SQLQuerier) GetBundlePathIfExists(ctx context.Context, bundleName string) (string, error) { - getBundlePathQuery := ` - SELECT bundlepath - FROM operatorbundle - WHERE operatorbundle.name=? LIMIT 1` - - rows, err := s.db.QueryContext(ctx, getBundlePathQuery, bundleName) - if err != nil { - return "", err - } - defer rows.Close() - - if !rows.Next() { - // no bundlepath set - err = registry.ErrBundleImageNotInDatabase - return "", err - } - - var bundlePathSQL sql.NullString - if err = rows.Scan(&bundlePathSQL); err != nil { - return "", err - } - - var bundlePath string - if bundlePathSQL.Valid { - bundlePath = bundlePathSQL.String - } - - return bundlePath, nil -} - -// ListRegistryBundles returns a set of registry bundles. -// The set can be filtered by package by setting the given context's 'package' key to a desired package name. -// e.g. -// ctx := ContextWithPackage(context.TODO(), "etcd") -// bundles, err := querier.ListRegistryBundles(ctx) -// // ... -func (s *SQLQuerier) ListRegistryBundles(ctx context.Context) ([]*registry.Bundle, error) { - listBundlesQuery := ` - SELECT DISTINCT operatorbundle.name, operatorbundle.version, operatorbundle.bundle, channel_entry.package_name - FROM operatorbundle - LEFT OUTER JOIN channel_entry ON operatorbundle.name = channel_entry.operatorbundle_name` - - var ( - err error - rows RowScanner - ) - if pkg, ok := registry.PackageFromContext(ctx); ok { - listBundlesQuery += " WHERE channel_entry.package_name=?" - rows, err = s.db.QueryContext(ctx, listBundlesQuery, pkg) - } else { - rows, err = s.db.QueryContext(ctx, listBundlesQuery) - } - if err != nil { - return nil, err - } - defer rows.Close() - - var bundles []*registry.Bundle - for rows.Next() { - var ( - bundleName sql.NullString - bundleVersion sql.NullString - bundle sql.NullString - packageName sql.NullString - ) - if err := rows.Scan(&bundleName, &bundleVersion, &bundle, &packageName); err != nil { - return nil, err - } - - switch { - case !bundleName.Valid: - return nil, fmt.Errorf("bundle name column corrupted") - case !bundleVersion.Valid: - // Version field is currently nullable - case !bundle.Valid: - // Bundle field is currently nullable - case !packageName.Valid: - return nil, fmt.Errorf("package name column corrupted") - } - - // Allow the channel_entry table to be authoritative - channels, err := s.listBundleChannels(ctx, bundleName.String) - if err != nil { - return nil, fmt.Errorf("unable to list channels for bundle %s: %s", bundleName.String, err) - } - - defaultChannel, err := s.GetDefaultChannelForPackage(ctx, packageName.String) - if err != nil { - return nil, fmt.Errorf("unable to get default channel for package %s: %s", packageName.String, err) - } - - b, err := registry.NewBundleFromStrings(bundleName.String, bundleVersion.String, packageName.String, defaultChannel, strings.Join(channels, ","), bundle.String) - if err != nil { - return nil, fmt.Errorf("unable to unmarshal bundle %s from database: %s", bundleName.String, err) - } - - bundles = append(bundles, b) - } - - return bundles, nil -} - -func (s *SQLQuerier) listBundleChannels(ctx context.Context, bundleName string) ([]string, error) { - listBundleChannelsQuery := ` - SELECT DISTINCT channel_entry.channel_name - FROM channel_entry - INNER JOIN operatorbundle ON channel_entry.operatorbundle_name = operatorbundle.name - WHERE operatorbundle.name = ?` - - rows, err := s.db.QueryContext(ctx, listBundleChannelsQuery, bundleName) - if err != nil { - return nil, err - } - defer rows.Close() - - var channels []string - for rows.Next() { - var channel sql.NullString - if err := rows.Scan(&channel); err != nil { - return nil, err - } - - if !channel.Valid { - return nil, fmt.Errorf("channel name column corrupt for bundle %s", bundleName) - } - - channels = append(channels, channel.String) - } - - return channels, nil -} - -// PackageFromDefaultChannelHeadBundle returns the package name if the provided bundle is the head of its default channel. -func (s *SQLQuerier) PackageFromDefaultChannelHeadBundle(ctx context.Context, bundle string) (string, error) { - packageFromDefaultChannelHeadBundle := ` - SELECT package_name FROM package, channel - WHERE channel.package_name == package.name - AND package.default_channel == channel.name - AND channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` - - rows, err := s.db.QueryContext(ctx, packageFromDefaultChannelHeadBundle, bundle) - if err != nil { - return "", err - } - defer rows.Close() - - var packageName sql.NullString - for rows.Next() { - if err := rows.Scan(&packageName); err != nil { - return "", err - } - - if !packageName.Valid { - return "", fmt.Errorf("package name column corrupt for bundle %s", bundle) - } - } - - return packageName.String, nil -} - -// BundlePathForChannelHead returns the bundlepath for the given package and channel -func (s *SQLQuerier) BundlePathForChannelHead(ctx context.Context, pkg string, channel string) (string, error) { - bundlePathForChannelHeadQuery := ` - SELECT bundlepath FROM operatorbundle - INNER JOIN channel ON channel.head_operatorbundle_name = operatorbundle.name - WHERE channel.package_name = ? AND channel.name = ? -` - - rows, err := s.db.QueryContext(ctx, bundlePathForChannelHeadQuery, pkg, channel) - if err != nil { - return "", err - } - defer rows.Close() - - var bundlePath sql.NullString - for rows.Next() { - if err := rows.Scan(&bundlePath); err != nil { - return "", err - } - if !bundlePath.Valid { - return "", fmt.Errorf("bundlepath column corrupt for package %s, channel %s", pkg, channel) - } - } - - return bundlePath.String, nil -} diff --git a/pkg/sqlite/query_sql_test.go b/pkg/sqlite/query_sql_test.go deleted file mode 100644 index 224b98297..000000000 --- a/pkg/sqlite/query_sql_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "testing" - - _ "github.com/mattn/go-sqlite3" - "github.com/stretchr/testify/require" -) - -func TestListBundlesQuery(t *testing.T) { - for _, tt := range []struct { - Name string - Setup func(t *testing.T, db *sql.DB) - Expect func(t *testing.T, rows *sql.Rows) - OmitManfests bool - }{ - { - Name: "replacement comes from channel entry", - Setup: func(t *testing.T, db *sql.DB) { - for _, stmt := range []string{ - `insert into package (name, default_channel) values ("package", "channel")`, - `insert into channel (name, package_name, head_operatorbundle_name) values ("channel", "package", "bundle")`, - `insert into operatorbundle (name) values ("bundle-a"), ("bundle-b")`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, depth, replaces) values ("package", "channel", "bundle-a", 1, 0, 2)`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, depth) values ("package", "channel", "bundle-b", 2, 1)`, - } { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("unexpected error executing setup statements: %v", err) - } - } - }, - Expect: func(t *testing.T, rows *sql.Rows) { - replacements := map[sql.NullString]sql.NullString{ - {String: "bundle-a", Valid: true}: {String: "bundle-b", Valid: true}, - {String: "bundle-b", Valid: true}: {Valid: false}, - } - for rows.Next() { - var ( - c interface{} - name, actual sql.NullString - ) - if err := rows.Scan(&c, &c, &c, &name, &c, &c, &actual, &c, &c, &c, &c, &c); err != nil { - t.Fatalf("unexpected error during row scan: %v", err) - } - expected, ok := replacements[name] - if !ok { - t.Errorf("unexpected name: %v", name) - continue - } - delete(replacements, name) - if actual != expected { - t.Errorf("got replacement %v for %v, expected %v", actual, name, expected) - continue - } - } - for replacer, replacee := range replacements { - t.Errorf("missing expected result row: %v replaces %v", replacer, replacee) - } - }, - }, - { - Name: "skips populated from multiple channel entries", - Setup: func(t *testing.T, db *sql.DB) { - for _, stmt := range []string{ - `insert into package (name, default_channel) values ("package", "channel")`, - `insert into channel (name, package_name, head_operatorbundle_name) values ("channel", "package", "bundle")`, - `insert into operatorbundle (name) values ("bundle-a"), ("bundle-b"), ("bundle-c")`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, replaces) values ("package", "channel", "bundle-a", 1, 2)`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id) values ("package", "channel", "bundle-b", 2)`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, replaces) values ("package", "channel", "bundle-a", 3, 4)`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id) values ("package", "channel", "bundle-c", 4)`, - } { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("unexpected error executing setup statements: %v", err) - } - } - }, - Expect: func(t *testing.T, rows *sql.Rows) { - type result struct { - Name sql.NullString - Replaces sql.NullString - Skips sql.NullString - } - expected := map[sql.NullString]result{ - {String: "bundle-a", Valid: true}: { - Name: sql.NullString{String: "bundle-a", Valid: true}, - Replaces: sql.NullString{Valid: false}, - Skips: sql.NullString{String: "bundle-b,bundle-c", Valid: true}, - }, - {String: "bundle-b", Valid: true}: { - Name: sql.NullString{String: "bundle-b", Valid: true}, - Replaces: sql.NullString{Valid: false}, - Skips: sql.NullString{Valid: false}, - }, - {String: "bundle-c", Valid: true}: { - Name: sql.NullString{String: "bundle-c", Valid: true}, - Replaces: sql.NullString{Valid: false}, - Skips: sql.NullString{Valid: false}, - }, - } - for rows.Next() { - var ( - c interface{} - actual result - ) - if err := rows.Scan(&c, &c, &c, &actual.Name, &c, &c, &actual.Replaces, &actual.Skips, &c, &c, &c, &c); err != nil { - t.Fatalf("unexpected error during row scan: %v", err) - } - r, ok := expected[actual.Name] - if !ok { - t.Errorf("unexpected name: %v", actual.Name) - continue - } - delete(expected, actual.Name) - if actual != r { - t.Errorf("got row %v, expected %v for name %v", actual, r, actual.Name) - continue - } - } - for _, e := range expected { - t.Errorf("missing expected result row: %v", e) - } - }, - }, - { - Name: "manifests omitted without bundlepath", - OmitManfests: true, - Setup: func(t *testing.T, db *sql.DB) { - for _, stmt := range []string{ - `insert into package (name, default_channel) values ("package", "channel")`, - `insert into channel (name, package_name, head_operatorbundle_name) values ("channel", "package", "bundle")`, - `insert into operatorbundle (name, bundle) values ("bundle-a", "{}")`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, depth) values ("package", "channel", "bundle-a", 1, 0)`, - } { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("unexpected error executing setup statements: %v", err) - } - } - }, - Expect: func(t *testing.T, rows *sql.Rows) { - require := require.New(t) - require.True(rows.Next()) - var ( - c interface{} - bundle sql.NullString - ) - require.NoError(rows.Scan(&c, &bundle, &c, &c, &c, &c, &c, &c, &c, &c, &c, &c)) - require.Equal(sql.NullString{Valid: true, String: "{}"}, bundle) - require.False(rows.Next()) - }, - }, - { - Name: "properties and depdendencies columns may be stored as sqlite type blob", - OmitManfests: true, - Setup: func(t *testing.T, db *sql.DB) { - for _, stmt := range []string{ - `insert into package (name, default_channel) values ("package", "channel")`, - `insert into channel (name, package_name, head_operatorbundle_name) values ("channel", "package", "bundle")`, - `insert into operatorbundle (name, bundle) values ("bundle-a", "{}")`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, depth) values ("package", "channel", "bundle-a", 1, 0)`, - `insert into properties (type, value, operatorbundle_name) values (CAST("blob_ptype" AS BLOB), CAST("blob_pvalue" AS BLOB), "bundle-a")`, - `insert into dependencies (type, value, operatorbundle_name) values (CAST("blob_dtype" AS BLOB), CAST("blob_dvalue" AS BLOB), "bundle-a")`, - } { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("unexpected error executing setup statements: %v", err) - } - } - }, - Expect: func(t *testing.T, rows *sql.Rows) { - require := require.New(t) - require.True(rows.Next()) - var ( - props, deps sql.NullString - c interface{} - ) - require.NoError(rows.Scan(&c, &c, &c, &c, &c, &c, &c, &c, &c, &c, &deps, &props)) - require.Equal(sql.NullString{Valid: true, String: `[{"type":"blob_ptype","value":"blob_pvalue"}]`}, props) - require.Equal(sql.NullString{Valid: true, String: `[{"type":"blob_dtype","value":"blob_dvalue"}]`}, deps) - require.False(rows.Next()) - }, - }, - { - Name: "manifests not omitted with bundlepath", - OmitManfests: true, - Setup: func(t *testing.T, db *sql.DB) { - for _, stmt := range []string{ - `insert into package (name, default_channel) values ("package", "channel")`, - `insert into channel (name, package_name, head_operatorbundle_name) values ("channel", "package", "bundle")`, - `insert into operatorbundle (name, bundle, bundlepath) values ("bundle-a", "{}", "path")`, - `insert into channel_entry (package_name, channel_name, operatorbundle_name, entry_id, depth) values ("package", "channel", "bundle-a", 1, 0)`, - } { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("unexpected error executing setup statements: %v", err) - } - } - }, - Expect: func(t *testing.T, rows *sql.Rows) { - require := require.New(t) - require.True(rows.Next()) - var ( - c interface{} - bundle sql.NullString - ) - require.NoError(rows.Scan(&c, &bundle, &c, &c, &c, &c, &c, &c, &c, &c, &c, &c)) - require.Equal(sql.NullString{Valid: false, String: ""}, bundle) - require.False(rows.Next()) - }, - }, - } { - t.Run(tt.Name, func(t *testing.T) { - ctx := context.Background() - - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - // TODO: this test should be rewritten to work with the foreign key constraints - _, err = db.Exec("PRAGMA foreign_keys = OFF") - require.NoError(t, err) - tt.Setup(t, db) - _, err = db.Exec("PRAGMA foreign_keys = ON") - require.NoError(t, err) - - rows, err := db.QueryContext(ctx, listBundlesQuery, sql.Named("omit_manifests", tt.OmitManfests)) - if err != nil { - t.Fatalf("unexpected error executing list bundles query: %v", err) - } - defer rows.Close() - - tt.Expect(t, rows) - }) - } -} diff --git a/pkg/sqlite/query_test.go b/pkg/sqlite/query_test.go deleted file mode 100644 index d354c40c4..000000000 --- a/pkg/sqlite/query_test.go +++ /dev/null @@ -1,264 +0,0 @@ -package sqlite_test - -import ( - "context" - "database/sql" - "fmt" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/sqlite" - "github.com/operator-framework/operator-registry/pkg/sqlite/sqlitefakes" -) - -func TestListBundles(t *testing.T) { - type Columns struct { - EntryID sql.NullInt64 - Bundle sql.NullString - BundlePath sql.NullString - BundleName sql.NullString - PackageName sql.NullString - ChannelName sql.NullString - Replaces sql.NullString - Skips sql.NullString - Version sql.NullString - SkipRange sql.NullString - Dependencies sql.NullString - Properties sql.NullString - } - - var NoRows sqlitefakes.FakeRowScanner - NoRows.NextReturns(false) - - ScanFromColumns := func(t *testing.T, dsts []interface{}, cols Columns) { - ct := reflect.TypeOf(cols) - if len(dsts) != ct.NumField() { - t.Fatalf("expected %d columns, got %d", ct.NumField(), len(dsts)) - } - for i, dst := range dsts { - f := ct.Field(i) - dv := reflect.ValueOf(dst) - if dv.Kind() != reflect.Ptr { - t.Fatalf("scan argument at index %d is not a pointer", i) - } - if !f.Type.AssignableTo(dv.Elem().Type()) { - t.Fatalf("%s is not assignable to argument %s at index %d", f.Type, dv.Elem().Type(), i) - } - dv.Elem().Set(reflect.ValueOf(cols).Field(i)) - } - } - - for _, tc := range []struct { - Name string - Querier func(t *testing.T) sqlite.Querier - Bundles []*api.Bundle - ErrorMessage string - }{ - { - Name: "returns error when query returns error", - Querier: func(t *testing.T) sqlite.Querier { - var q sqlitefakes.FakeQuerier - q.QueryContextReturns(nil, fmt.Errorf("test")) - return &q - }, - ErrorMessage: "test", - }, - { - Name: "returns error when scan returns error", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanReturns(fmt.Errorf("test")) - return &q - }, - ErrorMessage: "test", - }, - { - Name: "skips row without valid bundle name", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanCalls(func(args ...interface{}) error { - ScanFromColumns(t, args, Columns{ - Version: sql.NullString{Valid: true}, - BundlePath: sql.NullString{Valid: true}, - ChannelName: sql.NullString{Valid: true}, - }) - return nil - }) - return &q - }, - }, - { - Name: "skips row without valid version", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanCalls(func(args ...interface{}) error { - ScanFromColumns(t, args, Columns{ - BundleName: sql.NullString{Valid: true}, - BundlePath: sql.NullString{Valid: true}, - ChannelName: sql.NullString{Valid: true}, - }) - return nil - }) - return &q - }, - }, - { - Name: "skips row without valid bundle path", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanCalls(func(args ...interface{}) error { - ScanFromColumns(t, args, Columns{ - BundleName: sql.NullString{Valid: true}, - Version: sql.NullString{Valid: true}, - ChannelName: sql.NullString{Valid: true}, - }) - return nil - }) - return &q - }, - }, - { - Name: "skips row without valid channel name", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanCalls(func(args ...interface{}) error { - ScanFromColumns(t, args, Columns{ - BundleName: sql.NullString{Valid: true}, - Version: sql.NullString{Valid: true}, - BundlePath: sql.NullString{Valid: true}, - }) - return nil - }) - return &q - }, - }, - { - Name: "bundle dependencies are null when dependency type or value is null", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&r, nil) - r.NextReturnsOnCall(0, true) - r.ScanCalls(func(args ...interface{}) error { - ScanFromColumns(t, args, Columns{ - BundleName: sql.NullString{Valid: true}, - Version: sql.NullString{Valid: true}, - ChannelName: sql.NullString{Valid: true}, - BundlePath: sql.NullString{Valid: true}, - }) - return nil - }) - return &q - }, - Bundles: []*api.Bundle{ - {}, - }, - }, - { - Name: "all dependencies and properties are returned", - Querier: func(t *testing.T) sqlite.Querier { - var ( - q sqlitefakes.FakeQuerier - r sqlitefakes.FakeRowScanner - ) - q.QueryContextReturns(&NoRows, nil) - q.QueryContextReturnsOnCall(0, &r, nil) - r.NextReturnsOnCall(0, true) - cols := []Columns{ - { - BundleName: sql.NullString{Valid: true, String: "BundleName"}, - Version: sql.NullString{Valid: true, String: "Version"}, - ChannelName: sql.NullString{Valid: true, String: "ChannelName"}, - BundlePath: sql.NullString{Valid: true, String: "BundlePath"}, - Dependencies: sql.NullString{Valid: true, String: `[{"type":"Dependency1Type","value":"Dependency1Value"},{"type":"Dependency2Type","value":"Dependency2Value"}]`}, - Properties: sql.NullString{Valid: true, String: `[{"type":"Property1Type","value":"Property1Value"},{"type":"Property2Type","value":"Property2Value"}]`}, - }, - } - var i int - r.ScanCalls(func(args ...interface{}) error { - if i < len(cols) { - ScanFromColumns(t, args, cols[i]) - i++ - } - return nil - }) - return &q - }, - Bundles: []*api.Bundle{ - { - CsvName: "BundleName", - ChannelName: "ChannelName", - BundlePath: "BundlePath", - Version: "Version", - Dependencies: []*api.Dependency{ - { - Type: "Dependency1Type", - Value: "Dependency1Value", - }, - { - Type: "Dependency2Type", - Value: "Dependency2Value", - }, - }, - Properties: []*api.Property{ - { - Type: "Property1Type", - Value: "Property1Value", - }, - { - Type: "Property2Type", - Value: "Property2Value", - }, - }, - }, - }, - }, - } { - t.Run(tc.Name, func(t *testing.T) { - var q sqlite.Querier - if tc.Querier != nil { - q = tc.Querier(t) - } - sq := sqlite.NewSQLLiteQuerierFromDBQuerier(q) - bundles, err := sq.ListBundles(context.Background()) - - assert := assert.New(t) - assert.Equal(tc.Bundles, bundles) - if tc.ErrorMessage == "" { - assert.NoError(err) - } else { - assert.EqualError(err, tc.ErrorMessage) - } - }) - } -} diff --git a/pkg/sqlite/remove.go b/pkg/sqlite/remove.go deleted file mode 100644 index 1d2a2cb4d..000000000 --- a/pkg/sqlite/remove.go +++ /dev/null @@ -1,66 +0,0 @@ -package sqlite - -import ( - "fmt" - "strings" - - "github.com/sirupsen/logrus" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type SQLRemover interface { - Remove() error -} - -// PackageRemover removes a package from the database -type PackageRemover struct { - store registry.Load - packages string -} - -var _ SQLRemover = &PackageRemover{} - -func NewSQLRemoverForPackages(store registry.Load, packages string) *PackageRemover { - return &PackageRemover{ - store: store, - packages: packages, - } -} - -func (d *PackageRemover) Remove() error { - log := logrus.WithField("pkg", d.packages) - - log.Info("deleting packages") - - var errs []error - packages := sanitizePackageList(strings.Split(d.packages, ",")) - log.Infof("packages: %s", packages) - - for _, pkg := range packages { - if err := d.store.RemovePackage(pkg); err != nil { - errs = append(errs, fmt.Errorf("error removing operator package %s: %s", pkg, err)) - } - } - - return utilerrors.NewAggregate(errs) -} - -// sanitizePackageList sanitizes the set of package(s) specified. It removes -// duplicates and ignores empty string. -func sanitizePackageList(in []string) []string { - out := make([]string, 0) - - inMap := map[string]bool{} - for _, item := range in { - if _, ok := inMap[item]; ok || item == "" { - continue - } - - inMap[item] = true - out = append(out, item) - } - - return out -} diff --git a/pkg/sqlite/remove_test.go b/pkg/sqlite/remove_test.go deleted file mode 100644 index 782b1bf5e..000000000 --- a/pkg/sqlite/remove_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package sqlite - -import ( - "context" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func TestRemover(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - query := NewSQLLiteQuerierFromDb(db) - - graphLoader, err := NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - populate := func(name string) error { - return registry.NewDirectoryPopulator( - store, - graphLoader, - query, - map[image.Reference]string{ - image.SimpleReference("quay.io/test/" + name): "../../bundles/" + name, - }, - nil).Populate(registry.ReplacesMode) - } - for _, name := range []string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"} { - require.NoError(t, populate(name)) - } - - // check that bundles not properly associated with a package are not left - b := registry.NewBundle("stranded", ®istry.Annotations{ - PackageName: "p", - Channels: "c", - DefaultChannelName: "c", - }, &unstructured.Unstructured{ - Object: map[string]interface{}{ - "kind": "ClusterServiceVersion", - "metadata": map[string]interface{}{ - "name": "stranded", - }, - "spec": map[string]interface{}{ - "version": "0.0.1", - }, - }}) - b.BundleImage = "bundle-image" - require.NoError(t, store.AddOperatorBundle(b)) - - // delete etcd - require.NoError(t, store.RemovePackage("etcd")) - - _, err = query.GetPackage(context.TODO(), "etcd") - require.EqualError(t, err, "package etcd not found") - - // prometheus apis still around - rows, err := db.QueryContext(context.TODO(), "select * from api") - require.NoError(t, err) - require.True(t, rows.Next()) - require.NoError(t, rows.Close()) - - // delete prometheus - require.NoError(t, store.RemovePackage("prometheus")) - - _, err = query.GetPackage(context.TODO(), "prometheus") - require.EqualError(t, err, "package prometheus not found") - - // delete prometheus again - err = store.RemovePackage("prometheus") - require.EqualError(t, err, "no package found for packagename prometheus") - - // no apis after all packages are removed - rows, err = db.QueryContext(context.TODO(), "select * from api") - require.NoError(t, err) - require.False(t, rows.Next()) - require.NoError(t, rows.Close()) - - // check there are no related images, included stranded csv - imgs, err := query.ListImages(context.TODO()) - require.NoError(t, err) - require.ElementsMatch(t, []string{}, imgs) - - // and insert again - for _, name := range []string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"} { - require.NoError(t, populate(name)) - } - - // apis are back - rows, err = db.QueryContext(context.TODO(), "select * from api") - require.NoError(t, err) - require.True(t, rows.Next()) - require.NoError(t, rows.Close()) -} diff --git a/pkg/sqlite/sqlitefakes/fake_querier.go b/pkg/sqlite/sqlitefakes/fake_querier.go deleted file mode 100644 index aff65723c..000000000 --- a/pkg/sqlite/sqlitefakes/fake_querier.go +++ /dev/null @@ -1,121 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package sqlitefakes - -import ( - "context" - "sync" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -type FakeQuerier struct { - QueryContextStub func(context.Context, string, ...interface{}) (sqlite.RowScanner, error) - queryContextMutex sync.RWMutex - queryContextArgsForCall []struct { - arg1 context.Context - arg2 string - arg3 []interface{} - } - queryContextReturns struct { - result1 sqlite.RowScanner - result2 error - } - queryContextReturnsOnCall map[int]struct { - result1 sqlite.RowScanner - result2 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeQuerier) QueryContext(arg1 context.Context, arg2 string, arg3 ...interface{}) (sqlite.RowScanner, error) { - fake.queryContextMutex.Lock() - ret, specificReturn := fake.queryContextReturnsOnCall[len(fake.queryContextArgsForCall)] - fake.queryContextArgsForCall = append(fake.queryContextArgsForCall, struct { - arg1 context.Context - arg2 string - arg3 []interface{} - }{arg1, arg2, arg3}) - stub := fake.QueryContextStub - fakeReturns := fake.queryContextReturns - fake.recordInvocation("QueryContext", []interface{}{arg1, arg2, arg3}) - fake.queryContextMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3...) - } - if specificReturn { - return ret.result1, ret.result2 - } - return fakeReturns.result1, fakeReturns.result2 -} - -func (fake *FakeQuerier) QueryContextCallCount() int { - fake.queryContextMutex.RLock() - defer fake.queryContextMutex.RUnlock() - return len(fake.queryContextArgsForCall) -} - -func (fake *FakeQuerier) QueryContextCalls(stub func(context.Context, string, ...interface{}) (sqlite.RowScanner, error)) { - fake.queryContextMutex.Lock() - defer fake.queryContextMutex.Unlock() - fake.QueryContextStub = stub -} - -func (fake *FakeQuerier) QueryContextArgsForCall(i int) (context.Context, string, []interface{}) { - fake.queryContextMutex.RLock() - defer fake.queryContextMutex.RUnlock() - argsForCall := fake.queryContextArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 -} - -func (fake *FakeQuerier) QueryContextReturns(result1 sqlite.RowScanner, result2 error) { - fake.queryContextMutex.Lock() - defer fake.queryContextMutex.Unlock() - fake.QueryContextStub = nil - fake.queryContextReturns = struct { - result1 sqlite.RowScanner - result2 error - }{result1, result2} -} - -func (fake *FakeQuerier) QueryContextReturnsOnCall(i int, result1 sqlite.RowScanner, result2 error) { - fake.queryContextMutex.Lock() - defer fake.queryContextMutex.Unlock() - fake.QueryContextStub = nil - if fake.queryContextReturnsOnCall == nil { - fake.queryContextReturnsOnCall = make(map[int]struct { - result1 sqlite.RowScanner - result2 error - }) - } - fake.queryContextReturnsOnCall[i] = struct { - result1 sqlite.RowScanner - result2 error - }{result1, result2} -} - -func (fake *FakeQuerier) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.queryContextMutex.RLock() - defer fake.queryContextMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeQuerier) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ sqlite.Querier = new(FakeQuerier) diff --git a/pkg/sqlite/sqlitefakes/fake_rowscanner.go b/pkg/sqlite/sqlitefakes/fake_rowscanner.go deleted file mode 100644 index b5ffcc8dc..000000000 --- a/pkg/sqlite/sqlitefakes/fake_rowscanner.go +++ /dev/null @@ -1,241 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package sqlitefakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/sqlite" -) - -type FakeRowScanner struct { - CloseStub func() error - closeMutex sync.RWMutex - closeArgsForCall []struct { - } - closeReturns struct { - result1 error - } - closeReturnsOnCall map[int]struct { - result1 error - } - NextStub func() bool - nextMutex sync.RWMutex - nextArgsForCall []struct { - } - nextReturns struct { - result1 bool - } - nextReturnsOnCall map[int]struct { - result1 bool - } - ScanStub func(...interface{}) error - scanMutex sync.RWMutex - scanArgsForCall []struct { - arg1 []interface{} - } - scanReturns struct { - result1 error - } - scanReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeRowScanner) Close() error { - fake.closeMutex.Lock() - ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)] - fake.closeArgsForCall = append(fake.closeArgsForCall, struct { - }{}) - stub := fake.CloseStub - fakeReturns := fake.closeReturns - fake.recordInvocation("Close", []interface{}{}) - fake.closeMutex.Unlock() - if stub != nil { - return stub() - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeRowScanner) CloseCallCount() int { - fake.closeMutex.RLock() - defer fake.closeMutex.RUnlock() - return len(fake.closeArgsForCall) -} - -func (fake *FakeRowScanner) CloseCalls(stub func() error) { - fake.closeMutex.Lock() - defer fake.closeMutex.Unlock() - fake.CloseStub = stub -} - -func (fake *FakeRowScanner) CloseReturns(result1 error) { - fake.closeMutex.Lock() - defer fake.closeMutex.Unlock() - fake.CloseStub = nil - fake.closeReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeRowScanner) CloseReturnsOnCall(i int, result1 error) { - fake.closeMutex.Lock() - defer fake.closeMutex.Unlock() - fake.CloseStub = nil - if fake.closeReturnsOnCall == nil { - fake.closeReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.closeReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeRowScanner) Next() bool { - fake.nextMutex.Lock() - ret, specificReturn := fake.nextReturnsOnCall[len(fake.nextArgsForCall)] - fake.nextArgsForCall = append(fake.nextArgsForCall, struct { - }{}) - stub := fake.NextStub - fakeReturns := fake.nextReturns - fake.recordInvocation("Next", []interface{}{}) - fake.nextMutex.Unlock() - if stub != nil { - return stub() - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeRowScanner) NextCallCount() int { - fake.nextMutex.RLock() - defer fake.nextMutex.RUnlock() - return len(fake.nextArgsForCall) -} - -func (fake *FakeRowScanner) NextCalls(stub func() bool) { - fake.nextMutex.Lock() - defer fake.nextMutex.Unlock() - fake.NextStub = stub -} - -func (fake *FakeRowScanner) NextReturns(result1 bool) { - fake.nextMutex.Lock() - defer fake.nextMutex.Unlock() - fake.NextStub = nil - fake.nextReturns = struct { - result1 bool - }{result1} -} - -func (fake *FakeRowScanner) NextReturnsOnCall(i int, result1 bool) { - fake.nextMutex.Lock() - defer fake.nextMutex.Unlock() - fake.NextStub = nil - if fake.nextReturnsOnCall == nil { - fake.nextReturnsOnCall = make(map[int]struct { - result1 bool - }) - } - fake.nextReturnsOnCall[i] = struct { - result1 bool - }{result1} -} - -func (fake *FakeRowScanner) Scan(arg1 ...interface{}) error { - fake.scanMutex.Lock() - ret, specificReturn := fake.scanReturnsOnCall[len(fake.scanArgsForCall)] - fake.scanArgsForCall = append(fake.scanArgsForCall, struct { - arg1 []interface{} - }{arg1}) - stub := fake.ScanStub - fakeReturns := fake.scanReturns - fake.recordInvocation("Scan", []interface{}{arg1}) - fake.scanMutex.Unlock() - if stub != nil { - return stub(arg1...) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeRowScanner) ScanCallCount() int { - fake.scanMutex.RLock() - defer fake.scanMutex.RUnlock() - return len(fake.scanArgsForCall) -} - -func (fake *FakeRowScanner) ScanCalls(stub func(...interface{}) error) { - fake.scanMutex.Lock() - defer fake.scanMutex.Unlock() - fake.ScanStub = stub -} - -func (fake *FakeRowScanner) ScanArgsForCall(i int) []interface{} { - fake.scanMutex.RLock() - defer fake.scanMutex.RUnlock() - argsForCall := fake.scanArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeRowScanner) ScanReturns(result1 error) { - fake.scanMutex.Lock() - defer fake.scanMutex.Unlock() - fake.ScanStub = nil - fake.scanReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeRowScanner) ScanReturnsOnCall(i int, result1 error) { - fake.scanMutex.Lock() - defer fake.scanMutex.Unlock() - fake.ScanStub = nil - if fake.scanReturnsOnCall == nil { - fake.scanReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.scanReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeRowScanner) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.closeMutex.RLock() - defer fake.closeMutex.RUnlock() - fake.nextMutex.RLock() - defer fake.nextMutex.RUnlock() - fake.scanMutex.RLock() - defer fake.scanMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeRowScanner) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ sqlite.RowScanner = new(FakeRowScanner) diff --git a/pkg/sqlite/stranded.go b/pkg/sqlite/stranded.go deleted file mode 100644 index 9bab472eb..000000000 --- a/pkg/sqlite/stranded.go +++ /dev/null @@ -1,36 +0,0 @@ -package sqlite - -import ( - "github.com/sirupsen/logrus" - - "github.com/operator-framework/operator-registry/pkg/registry" -) - -type SQLStrandedBundleRemover interface { - Remove() error -} - -// StrandedBundleRemover removes stranded bundles from the database -type StrandedBundleRemover struct { - store registry.Load -} - -var _ SQLStrandedBundleRemover = &StrandedBundleRemover{} - -func NewSQLStrandedBundleRemover(store registry.Load) *StrandedBundleRemover { - return &StrandedBundleRemover{ - store: store, - } -} - -func (d *StrandedBundleRemover) Remove() error { - log := logrus.New() - - err := d.store.RemoveStrandedBundles() - if err != nil { - return err - } - log.Info("removing stranded bundles ") - - return nil -} diff --git a/pkg/sqlite/stranded_test.go b/pkg/sqlite/stranded_test.go deleted file mode 100644 index c39081867..000000000 --- a/pkg/sqlite/stranded_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/registry" -) - -func TestStrandedBundleRemover(t *testing.T) { - logrus.SetLevel(logrus.DebugLevel) - db, cleanup := CreateTestDB(t) - defer cleanup() - store, err := NewSQLLiteLoader(db) - require.NoError(t, err) - require.NoError(t, store.Migrate(context.TODO())) - - query := NewSQLLiteQuerierFromDb(db) - - graphLoader, err := NewSQLGraphLoaderFromDB(db) - require.NoError(t, err) - - populate := func(name string) error { - return registry.NewDirectoryPopulator( - store, - graphLoader, - query, - map[image.Reference]string{ - image.SimpleReference("quay.io/test/" + name): "./testdata/strandedbundles/" + name, - }, - nil).Populate(registry.ReplacesMode) - } - for _, name := range []string{"prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"} { - require.NoError(t, populate(name)) - } - - // check that the bundle is orphaned - querier := NewSQLLiteQuerierFromDb(db) - packageBundles, err := querier.GetBundlesForPackage(context.TODO(), "prometheus") - require.NoError(t, err) - require.Len(t, packageBundles, 1) - - rows, err := db.QueryContext(context.TODO(), "select * from operatorbundle") - require.NoError(t, err) - require.Equal(t, 3, rowCount(rows)) - require.NoError(t, rows.Close()) - - // check that properties are set - rows, err = db.QueryContext(context.TODO(), `select * from properties where operatorbundle_name="prometheusoperator.0.14.0"`) - require.NoError(t, err) - require.True(t, rows.Next()) - require.NoError(t, rows.Close()) - - // prune the orphaned bundle - err = store.RemoveStrandedBundles() - require.NoError(t, err) - - // other bundles in the package still exist, but the bundle is removed - packageBundles, err = querier.GetBundlesForPackage(context.TODO(), "prometheus") - require.NoError(t, err) - require.Len(t, packageBundles, 1) - - rows, err = db.QueryContext(context.TODO(), "select * from operatorbundle") - require.NoError(t, err) - require.Equal(t, 1, rowCount(rows)) - require.NoError(t, rows.Close()) - - // check that properties are removed - rows, err = db.QueryContext(context.TODO(), `select * from properties where operatorbundle_name="prometheusoperator.0.14.0" OR operatorbundle_name="prometheusoperator.0.15.0"`) - require.NoError(t, err) - require.False(t, rows.Next()) - require.NoError(t, rows.Close()) -} - -func rowCount(rows *sql.Rows) int { - count := 0 - for rows.Next() { - count++ - } - - return count -} diff --git a/pkg/sqlite/testdata/.gitignore b/pkg/sqlite/testdata/.gitignore deleted file mode 100644 index 7c77c5ebf..000000000 --- a/pkg/sqlite/testdata/.gitignore +++ /dev/null @@ -1 +0,0 @@ -manifests-* \ No newline at end of file diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/3scale-community-operator.v0.3.0.clusterserviceversion.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/3scale-community-operator.v0.3.0.clusterserviceversion.yaml deleted file mode 100644 index 7ca914ee6..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/3scale-community-operator.v0.3.0.clusterserviceversion.yaml +++ /dev/null @@ -1,317 +0,0 @@ -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - annotations: - alm-examples: '[{"apiVersion":"apps.3scale.net/v1alpha1","kind":"APIManager","metadata":{"name":"example-apimanager"},"spec":{"wildcardDomain":"example.com"}}, - {"apiVersion":"apps.3scale.net/v1alpha1","kind":"APIManager","metadata":{"name":"example-apimanager-ha"},"spec":{"highAvailability":{"enabled":true},"wildcardDomain":"example.com"}}, - {"apiVersion":"apps.3scale.net/v1alpha1","kind":"APIManager","metadata":{"name":"example-apimanager-s3"},"spec":{"system":{"fileStorage":{"amazonSimpleStorageService":{"awsBucket":"\u003cbucket-name\u003e","awsCredentialsSecret":{"name":"\u003ccredentials-secret-name\u003e"},"awsRegion":"\u003cregion\u003e"}}},"wildcardDomain":"\u003cdesired-domain\u003e"}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"API","metadata":{"labels":{"environment":"testing"},"name":"example-api"},"spec":{"description":"api01","integrationMethod":{"apicastHosted":{"apiTestGetRequest":"/","authenticationSettings":{"credentials":{"apiKey":{"authParameterName":"user-key","credentialsLocation":"headers"}},"errors":{"authenticationFailed":{"contentType":"text/plain; - charset=us-ascii","responseBody":"Authentication failed","responseCode":403},"authenticationMissing":{"contentType":"text/plain; - charset=us-ascii","responseBody":"Authentication Missing","responseCode":403}},"hostHeader":"","secretToken":"MySecretTokenBetweenApicastAndMyBackend_1237120312"},"mappingRulesSelector":{"matchLabels":{"api":"api01"}},"privateBaseURL":"https://echo-api.3scale.net:443"}}}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"Binding","metadata":{"name":"example-binding"},"spec":{"APISelector":{"matchLabels":{"environment":"testing"}},"credentialsRef":{"name":"ecorp-tenant-secret"}}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"Limit","metadata":{"labels":{"api":"api01"},"name":"plan01-metric01-day-10"},"spec":{"description":"Limit - for metric01 in plan01","maxValue":10,"metricRef":{"name":"metric01"},"period":"day"}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"MappingRule","metadata":{"labels":{"api":"api01"},"name":"metric01-get-path01"},"spec":{"increment":1,"method":"GET","metricRef":{"name":"metric01"},"path":"/path01"}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"Metric","metadata":{"labels":{"api":"api01"},"name":"metric01"},"spec":{"description":"metric01","incrementHits":false,"unit":"hit"}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"Plan","metadata":{"labels":{"api":"api01"},"name":"example-plan"},"spec":{"approvalRequired":false,"costs":{"costMonth":0,"setupFee":0},"default":true,"limitSelector":{"matchLabels":{"plan":"plan01"}},"trialPeriod":0}}, - {"apiVersion":"capabilities.3scale.net/v1alpha1","kind":"Tenant","metadata":{"name":"example-tenant"},"spec":{"email":"admin@example.com","masterCredentialsRef":{"name":"system-seed"},"organizationName":"Example.com","passwordCredentialsRef":{"name":"ecorp-admin-secret"},"systemMasterUrl":"https://master.example.com","tenantSecretRef":{"name":"ecorp-tenant-secret","namespace":"operator-test"},"username":"admin"}}]' - capabilities: Deep Insights - categories: Integration & Delivery - certified: 'false' - containerImage: quay.io/3scale/3scale-operator:v0.3.0 - createdAt: 2019-05-30 22:40:00 - description: Install 3scale and publish/manage your API - repository: https://github.com/3scale/3scale-operator - support: Red Hat, Inc. - tectonic-visibility: ocs - name: 3scale-community-operator.v0.3.0 - namespace: placeholder -spec: - customresourcedefinitions: - owned: - - description: API Manager - displayName: API Manager - kind: APIManager - name: apimanagers.apps.3scale.net - resources: - - kind: DeploymentConfig - version: apps.openshift.io/v1 - - kind: PersistentVolumeClaim - version: v1 - - kind: Service - version: v1 - - kind: Route - version: route.openshift.io/v1 - - kind: ImageStream - version: image.openshift.io/v1 - specDescriptors: - - description: Wildcard domain as configured in the API Manager object - displayName: Wildcard Domain - path: wildcardDomain - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:label - statusDescriptors: - - description: API Manager Deployment Configs - displayName: Deployments - path: deployments - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:podStatuses - version: v1alpha1 - - description: API - displayName: API - kind: API - name: apis.capabilities.3scale.net - version: v1alpha1 - - description: Binding - displayName: Binding - kind: Binding - name: bindings.capabilities.3scale.net - version: v1alpha1 - - description: Limit - displayName: Limit - kind: Limit - name: limits.capabilities.3scale.net - version: v1alpha1 - - description: MappingRule - displayName: MappingRule - kind: MappingRule - name: mappingrules.capabilities.3scale.net - version: v1alpha1 - - description: Metric - displayName: Metric - kind: Metric - name: metrics.capabilities.3scale.net - version: v1alpha1 - - description: Plan - displayName: Plan - kind: Plan - name: plans.capabilities.3scale.net - version: v1alpha1 - - description: Tenant - displayName: Tenant - kind: Tenant - name: tenants.capabilities.3scale.net - version: v1alpha1 - description: 'The 3scale community Operator creates and maintains the Red Hat 3scale - API Management - Community version on [OpenShift](https://www.openshift.com/) - in various deployment configurations. - - - [3scale API Management](https://www.redhat.com/en/technologies/jboss-middleware/3scale) - makes it easy to manage your APIs. - - Share, secure, distribute, control, and monetize your APIs on an infrastructure - platform built for performance, customer control, and future growth. - - - ### Supported Features - - * **Installer** A way to install a 3scale API Management solution, providing configurability - options at the time of installation - - * **Capabilities** Ability to define 3scale API definitions and set them into - a 3scale API Management solution - - - ### Upgrading your installation - - Currently upgrading feature is not available. - - - ### Documentation - - Documentation can be found on our [website](https://github.com/3scale/3scale-operator/blob/v0.3.0/doc/user-guide.md). - - - ### Getting help - - If you encounter any issues while using 3scale community operator, you can create - an issue on our [website](https://github.com/3scale/3scale-operator) for bugs, - enhancements, or other requests. - - - ### Contributing - - You can contribute by: - - - * Raising any issues you find using 3scale community Operator - - * Fixing issues by opening [Pull Requests](https://github.com/3scale/3scale-operator/pulls) - - * Improving [documentation](https://github.com/3scale/3scale-operator/blob/v0.3.0/doc/user-guide.md) - - * Talking about 3scale community Operator - - - All bugs, tasks or enhancements are tracked as [GitHub issues](https://github.com/3scale/3scale-operator/issues). - - - ### License - - 3scale community Operator is licensed under the [Apache 2.0 license](https://github.com/3scale/3scale-operator/blob/master/LICENSE) - - ' - displayName: 3scale - icon: - - base64data: iVBORw0KGgoAAAANSUhEUgAAAOsAAADoCAYAAAAOnhN7AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACC2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+MjwvdGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KD0UqkwAAPCdJREFUeAHtfc1y20i2JinZVa7pDSumerYNPUB3UU9gajezMr3r7uoIk09gaTdd90aIjJiovjtLTyA4+ndWop9A9BOIFf0Awt12dYfZi7ltV9nifB+dUIEQkDj5AxAkkREggcxzTp48mSfPyR8k2q0m1FoCv/nNb3q3t7fPwGQPV4ArGaLFYjHBdf6Xv/wlSiY099sngfb2FWk7SvTLX/4yaLfbF7h6whKFn3zyyUkYhnMhfAO2YRJolLWGFfarX/2qT0UFax1D9qioR3/6059mhngN+AZIoFHWmlWScnuvHNgSK6zKq4e8vsT1LdztsHGnHSRfMmqjrCUL2IQ8Xd+9vb1r4Jha1HQ2EVziQ51L/Otf/5qWe5BGxPMYlnmUEd9ErVkCe2vOv8k+IQEo6ikeXRWVFIN3796RVmaAoo6QMMhMbLVO6YbnpDXRa5RAY1kdhI9G3wX6XcPGrOzsz3/+88SGpLKqNza4eTiwrp9nWVfw/QY4nTw8xM9gXQ816U3SGiTwYA15bnyW8UwtCtJLFgaTQi0oQoS4p6aTPLCqgyQtH/c//PADaZ4laalxqk5RCd5N4jT39ZBA4wYb1kM8rtQsqQQgeaWsrpg6rPJjMbAQsAyawqwbsBIk0FhWQ6EKl1RouS5xHUjJg24Z1uwezT/84Q9TdCRz8EUe88IsL8Emnh3c/v5+H53Hl7i4fsz8v8U1MfVAbPLfFpzGshrUpHJ/e0KUwHCiRqc8wizvgQX3Yj5GnOfEL6OhUGNdujRtMBh0IIMXcPFvQPMF8AbKI+njnhNg10i/olxx34QCCTTKWiCgZDKtQ/K56L4ka1mUbWG6WpoJcwDHtpNkSXpU1O+///4KMjhOxqfvqbxcrjIdNqTp7MJzo6wGtQzrYGT9AG8yDo0MWJGCzvIAobBDKMkR0sfgc8p/bIo4UIqchyaKjxUVwF0RwkeXvLGwBcJqxqwFAkomwwrM0bCTUdp7wL/WAqwmUrGC1Sjnp0hHgeNXpPPyGmBRj0FQqqhx3h3I6wIP7ECakCGBxrJmCCUvSlmgvOSs+CgrMivOULGzSGTFvcqKrCDuuU0ekEGvcYfzJbcTllU1gH4sBijd7NNPP51mbRiIYbL+OXOJCZEpG1VWeiouAnyYist9fPjwYQiLxEkYX2GOTRETX8SkdITruDpyrKeZDmBX07ZaWVXDoWsVJCsYytaCYsyhxOdo0GcmSgtFHwL/GvQ6SZqp+zmen6bitI/kAfyMAXSqBZQnnpuUS05WD4lxb08PUZj6ZSHEjgJsrRuMhj9Cw7lCvQY5dUtlO+WMJSdEcmDuRau3Ujiumt1L/BgR4c/qNTV2HMAlvmvgdsGRK5F14KMzFNfFOvhbZ55baVmhqAMIVWqhulRYwB9KK4LuMOETr5gtUeleuyx7KOtKi0x+bBvtDErPzmQjA7yW+UYyXgHT7QryqDSLeDsgMjVt7LV5NQydTRf8X+IKcJmEpaKuw/2NmVQdGDsb21CberAtQFl4W2dZ1YZ4U0WlfJ/jGvFm3YGWG675IV9zg6U5FvAzB8x5HVxf4XbG3CLxBfjcxESC2k3GOmPH1lIW+RVkEPJ5G8PWWVZYpWtU1LICTSuMmwTU2qMpamnwHE/z7Rm42I85nkOj7CEzHpQW4T7C/esyG2ja1adSfPjwYaI7UYLzBeBLOgwB6F3gXuHCibkC+jNQG4IO/7cqbKOyLhxqiJUcOuBvDSr3NUMxX6BAQU6hJrCCJ3lKa9FpcqnpoMiF/+qrr47RUZEvXRDR0hGoY9rWzgbbCBuNs2ODt204UDSeqniJcgWasvV1e3rVJJfUus2Rz1GRotLLgKJKLDb3JRcptKZo9UzaRmVlxVsFNFBp47KivwlIfEsGfA6EvHYAl/nuLhUPXgpn2Me4dHXCI1QPJG4rFLAPWsxTEgi7VWHrJphQO1NcVhX14MGD2VbVrmFh1Pj02BCtA/gLXJlLX1DCESziGSbLeugMuwnaPNRtUmRNE/C8DVLPukfytVVh65QVrtk5xlJ9i1pCu9ntA7Iht+cWciNKF67zIG+8r+Q6ARyvJlhKYOvcYM7mYlwzNZTHHA2V7tquB5tOLpbZk/imrH9Y5rkBbRNYA7LrA62tZeU6GtzSgKKhe2pi9bBJ/6naldSViBbKPcyb1ZTgbwOMcoFdiiKStUsGXDKC5ySdOJq45FVH3HadmFLLBc/AUw9XJ8VbhOcpKuulZC2UM4cYJ12iN+6l6CQfI9AbSuglkepyr3ZrLddgY55QnldF66AxbPLfw86jFtzg0tsTJ8BQp8dJ3jPu6SkdblsHXLpwMwR5LwrjnS7XzgoU6w6Pbq7UGqpGyA6APT+vCNcM10bvdhE02jEnd1BOUfCgrHwl8ECUmSMQl5ZAYpBDZo74I/DCOt6qsHZl5cQEJErhm4atrZQiQRQ01iR6iEY7TEbk3dMTwdDhTV66IF60+0hARwSiOhdOiPVwdXBROV9hhtnolUfgbExYq7I6KGos4J1TWMhshMKfxgIo+ocH8lT6JhCsNQ846xXRzElvdn/lCMZX9Npmg9HouiiEdLIgr7zsUS9pFfIAtilelZPWRBygfCYyPhcTXgXk9r7JalTz5FsCa1NWjlFRGB9KFsB9O/YtmDrSM9zBExchUB1j/Jz7ryxwmAuQk8D5A5PZ+hwyTXSBBNairBxvOLhbWUV6viPWNcgqfFEcZojFnSIs5AmUb1pEM5E+lLrZCZzm1kICa1lnxbQ6Z2d9Bk6O9EEw9ElUQktNzDDvJ7i6uAJcy6Aa/Qwd08tNmZ1UFvJIjY3pcucpuvdlL+UBUJbLAPk5nbwR09mW/7UoK4TXK0GAj0EzLIFuLkk2aHQSuQ1aeQ89EDjG5M0UzyeborTgc4SO6IydIJTmS5SBHVELZXgNSz31uTbNDo9r4iDfYx5xQF7WX+WLaWzT/1pmg9HIF76FSCsGd+zIN90sesqaXiFt2YCzYDRx1rOmyvJca2hnJsG1zfxOayZwxZFCWc7B1launZqIe12W1YTHWsEKG5eO5wsoHXf7hDqgrDRaZeBGSAuy0nPiTN9sySFTTjQs9wtQLur0+B4r4UrtjFm3PJUDw7QnsOrkqYNrhivCtfZNNGuZYELBNzYod62ocRWV7wJucb8IKCe98NiTBB633Z0knut4O5AwBeUp9bR+dIIc0tywU2Be4ImKysC6Zl2xk73h5Cju1xIaZTUQO48UURVpgJUNCjov2JNnp+bHqjHvEBDzfKhlCtNzj10pwK0k2bTh+5J9unBQwgvEneIqqo8AnR9fth+kaVTxvBZl5fjSd+FQka9900zTA9+sUF8hoMtlQ0y50HQJJ1n4Sr4c44VZ6Zsah3IVKZNx0bjHGkgDQ0QXz8gwqx/B16KsfDPkRxa83WU2XF/UldvqtbGg8T235Y8WFtdT9PQHoEPXeIxryGdOtDHdlnZVeO/fv48M8zKF15JX6/3HWqCcRBiHCxvPKIecKHotE0yG7yVKCsI3PkptnKicJxJGDGECvubm8iqXwo2Q78Qw77WDk3e4lKy3roQZ31sa0Q55LrMk6yyY5WQUEs6yEsuIW4tlVQ2MlsBLgGUpfRIFeQRemE0RiV+wT0XvzCO8LFHdQf5e36ahVYSi9lwEDS+mjA48l6W1KCu5QS/JHom9qmuYVLHdDRUr6v1NC4MK75nibBO82lwx1JUJiso1dJFS6+gk0+CCO9enq7In+ZHcr01Zua0NDZVjrbmE0RwYfttFW9E5eDbRHRukBqdYAhjChLCwR1TKFHQEhTiparNLKu/aPa5lzBpLge4w3JEDrG/Z7AaaUFErfNtjBr6de+O47In/KHG/s7fKwk4pAE78mJ67tQuCW6uyUsBK2Q7VZxEka138zgt720nFFRQhP+/KCotCuk1ISMDnvuME2TJu52UQzaNpPRWWR9A1Xi2RPAadFcWAO/QacTw6ZOaahw2+8BsrpqR5av3nJkiYPaVc+rh+pvD+E/9rk4viYSP/IMs3YLzjwDzlzqFcJWHtljVdSmUxJ+n4dT+XsNzEIonLydlLDBcugENFTYdTdHJTHsFa4bAgzcMmPlP+A1vG4RWd2+La4K1tgsmG2XXiqOWm0CcPmGAbS+gpRb0CbJaiLklwZpJ7W7luK6HZwLRaSv5zG1lwMqxqd712ltVUcGqfJte7erg6uCJcU1aEUjA8+gmkid6UCsN8nAIq+0zKnzr/uCvIsAP+LgF3KIDdaBB2YJALTxxZkQvKL1Yiyh8eyRA0KDOTMKcXY4LgA7Z2Y1ZpoVRlXdKi5OEg7eSPf/zjWV66TTzH1BaVm86KS06FnzgkkmV+1u/Mphnlc/IwcZSdytFhPK0Lnjnh96qqCT/FyymyH5CHnDBH/DnX8iXDAtXhvwDOslw5NOPoGW4oX/5XGkpRViXQHkoSqNJ4nwCBgK9Bu6vo6/68NlxmpCqX40ebIFZUEkdezGfAe4PgZeKD9Qhl5Ldae4K8S5+lhyxG4IOKKg1zdCSiM6IEZZ0jU3EHIGXQBM67ssIS8H3A4wwm2BN56ZEMlaWUTymAB3YUdJ8CXKJA1xfu01jS28cEIU+bs3ydT8dXMpZam5hd/osPFk8iFd2DH5tOKyYr7rAzDA09iBnqbWpSb3HGPv+9KqtAoOydjlxdCORzAzqBVBDoPLy7w3HeqlE/xzOVNyuwzBPbMbSlss5Nl4SSjKsyUTmsAhq31yN2BO1KwqdYYSXE1gHjbTaYYysUYFBQiPh4jgKwwuSgECIBAEV5knj0eguloCU5RB4HmNw4YseADMZosE/5TKXBVelX6mgJbAsJxWCnY62ozBcy6NHDsuUhiSdsV0mUvPsLWs28xE2I9zYbjAp6JikwK5JCk86EpmlyKxoUIx299mdVngiMTH0yA4V/BeXrGdK0Vlbk46SoMZ+o52PU1SvX5Q3Q8cIP+YIs2YGUNourXOgB6utxLAf+owyv8ec8b+PNsoKZHi5RcHktjHtGRZlsCRA3Y6Aoc5PioLGcm8DHsMr97cbPrv98X9SFhuKn40Ijhdsvw7qSJocr6AxukB/fke0lL8bhuiYMjQ3urYJPZfUp1NzCqEF+lAuQkaB6toyU+kfRYoP/sQGnLuvLXocLbLCOyuGVH8pwf3+/byDLQlB2KFDSa5a1CJgw8Aqtz3DypqzozadFzHpMf2lCCwIKTeDrBqvWigsVFnVwhvHxyIF/rw2ZfKAh9/hvGXqWeLloaRc1F1CQoMbTFwA1NVQ8KXEgyGIFxJuyoteQKlDkOo5RDXK2UpKcB/BV6xP+cti+F80yo+EfIWFyLxFxTMPGBE5uWQUX96wgw6AgXZdsqgQ6Wss0KKsXmvQY0LaoqLbhwlTm3iaY0JhC9BbPwXlXxz2EZd2gknS5A0jwHuzY9w6mJA9V36tObsp8IesuFLTj2vFVUIYvK8ij8iwge45DnRRfjemnUua9WVZmSAWCMuoyF+0mkTDPsSs6iEPADlN5zhEX0tIoCywht3EwKNtsAxSVGwqcGnQdK4ZbXcHXwJU3jmHZ6UrpeLOszFBN/hzRlwcjzxIVNcP9ue1yja4wtOjMWgfjI+3N/34UvH/QCmJaDx48mn0+mrNjaIJGAmgHrzXJRUmULxXDWwA/znUGj67vjaGPb1LNJPS8KmucYV3fSY35k/z/7eufdNuL98/arXa31W71PgCpnVje/fD929bfv36E3eyt6aLder1o7U/+xzf/TyR0Sf5Vw9BKo5f3nq2jckzBkE/FiNc8XcsZuBKI8WHEHsf3Rf9e3eCizDYhHQo4wHWz1/rA6fhjKqqWb6S3sY5G+H98/ek18bXw9U703tmodWLbUr+yRczDc+QnJruWcXijrEr8tKRUNjxyhi9Q0UZ/C1ph4P/9t4+uSM8IuR7A5z7ZgNWYugx91BAn8shT6MKPRz6sSDXKCrH9498+O6ZlVMpmJcgVJFhb0ts0K4sJwgnKMV8pi8MDNiCMHdCXqFB4L6sHIMa3r5z5UeX51rVcNvg7r6xQqAs0iBc2whPgXJC+AK4WIJwghCyGnpiZ+JitVvMfoStPLJdHqxq58hPjm0zAtWOkuvxzWjx5XAeWYMTHdJiWQSnSwBTPFB4N5eynv3vny0KYZm8Mj9n8vHeSpbSMXrCXEMXkFzu9gQQ2A8br63Fso5gRfpORj03UIZfhJIi1UlbN2cERCvNUWihJwen6lmhRs1gYfvHN2zAroY5xtgoLmZZ2yiIUdgRZnRrIK0JnP/Rh4dN5OnYeS3KUFTyHozTtvOfauMGsCKU8nQxmA8RxA3Q3I804ipM/FSsqeFy84FqtMbNrQuDWRcjoKbKPhCxwrDtm41Pr7UI0ORg66xHGnQfACHExv7zAlx9OMAY/LENRmSlo01PS8ZDH2108ebx7ENxYW1bua0RlstHP0Xs57fflPkvQuBHw63xcCfPgrK+3ySQB03cgWJP94ndvxT3pHR5uKG8sOzzB7V2HhcqO8PxazZritpyATnIAysy7h6uDKw4c485Qd68ePnwIHQ2dGm9MVPpPmUB5A8Dz4m6pGS+PY1OSzQ3KeFwBICmTXPhUgrFrbqSsajzJ9/UGGQyyos7Z++HfKJi4XGgYRy69pZqhvTBi0CPwYq919NP/83YqJckGgQbIMWRPgxMBZh2fFNGwtBtJlgprrKiUptgNVoNqHt51DLysnoRxp2D+mrAkbhC6Ulj0pD0pbA7caU58JdHtD/IxF2Q5AFOSA9P4BsglO71KCtFkcicBzqPAJT5ARHgXmXODDnWKJE4ohTkg2mixsgrecIkz6vJQ6vihTv9qo0KwVp6wBisZu6oem8on7vjYkSoFX2sRdy1zuv9QwCHH06gDjkNDKqZSzgnjmMbxPJXbVj6ivcGqAYitH5jj2wQDaQ8C+LlBASID2BVQ7vXF5tCVuHU83O63+8j3rCBvuuqdApis5BeYA3DaOZRF1Gfc8qWIvdagvWg9brcXHc4ftFscb7bn3Gf94LYVfv4fbyOfeVZBS42Vi+rVmhWpZX1ukcMTA5xXUli1y0YKvgK3B6u2ErGmh8XtQisbuLNU5q4lex2M6weWuKWiUUm5FfPDXusGXeYp913HE33LfzwznukbvGWzNBlKldWm4bDBiYKywDMBsNEB2Wl6ccNIx1f+3F5o5QlPQ6vMAn5d8QVZmIFwYu/D3uKaCirCBNwmbtkUlc0SqNANVmMnS/JyNFjMopMfeD7vSE5xFZK9Ol9zq0dodwr40CpzAS6TXfEFWchBfpyBtxqCcMtmy8eGErXp5llCPnPcT+GJnLusMMgl4QZZaFmhRJFbFjJsNUg/BPTy5IfEAD0+9WEoo5QNlXxxPBui2tjv/v1RT5NjrZRNw2dhkionx98uAW8y/be+LQEaHFzXaFMvQCMp2w6e+5j84YabC4tVDFuWrPAKLSuVCAWZgzoLZhJmJsAxrHKJw/h5R/9t5F07Ub0ZdTp4Sd9VUT+Wq/3hAvSmpqdzxEuOIFLUfgdY8WBeTkbhI7Pl/BZaVpXtxCJ78aSRBe2tRoEFsOroEkKhsq89vP/+7TGYCPww0u4oekbkoIC0ph0h0gCGaSCErRxMpKxwE8aGnM3hPp8Z4pQKvrjdr0UDjgup28XE7XsxnOX/1BLPK1q71eL40FswpacOGB8YMvDcEL4ycJGyqvWjoZQrWIZh1ftEi3jbpPORXI8e4YRJkTzKTlcbPwLP+QQmJ3BYnr7fVUrumXV3coVj1jgLjiWx/jfHsgLHIHluRWmvJMV8uPwvF95b7a4LDS+42NCvo8POEe4YvZlTHVxWGifm6jCz+aG9BzknTpjLYtYiDss5rL+ZBBWy6Ejg0jDqW0xROt70Wb1oQEvdwxXzMsf9BB3qS9N6EllWEF8GbJeawL09gMKeIGLChsGL97iGSCvtlSTQdw63BUrinIGQAHfpFIFymUrJtgg0mT7HR3+fJiPWdb9o33bLyHvhbQxcBnc/0uQ+bc4yI6aPq/NjyvJ+wDTTvdxiyxpnptzbMzzz2qjQbu1DSW6P1800jy2V8MC9pFxSAOxAAO/9dAZBnnUHiWwYNLV46TyohDBohe2MMKjff0r3Dxgra5qxTXr+4nf/NcECewSeg3XxTVfcZPyMihzCnXqJnpjuVD+D7xni+GpimJEmiuIYjeM7WPLHuDoxEhoTPQCr74q25S+tx9mJ/vcM9pHD05tgNpidnUmYmACnYZXre5yO1zzzTTWRjHdKWZXAxvg3rUCNrM2SsOXRePJH9fRTrhm+f/++G+fIb9W6TOSRnlraGEBJl2ShoDF5/vdwncJSTBF/gg5hxkhJwHu7UfJQdAmOBOa2LV/WomzA+xktmIQ2YVwn51SnKs0uhmNHPIwf8v5XaiYPaNviYV1vUKag6nLRqv73b94dVp1vVn7ozan0V7g6WekZcXPEUWHDjLTMqL9//ekbfMdASj+TxmrkYv7FN+8+X43TP6kOieVkebWBHZLrh8wg14+9njane4l8xa6wXEYTTPey2NAI9PqFvVgZRbvdMztzpwweSNNCUYlGpbuAperzQRbaExmcFMqcHq0r3OEj5KDjZY70oauiSkuRAdfJiLsXtZPKyg0JcPvO7kmjxAh0t2PdRogSs84ifYFIUQNJI8P6iPfQ7t+2xml8l2dbelRYWK6nyPuQ9Y7rbhWD1pQrHCYeg0sZXHB3ccy6lBfP8YWbFsBN67sIUIgb/vSbtyMhbKlgsKoDZNB1yITj3GPgj4po8AXy7377qdGYMY8mOzvXF9KhkDPQ51W3MJcwtJOWNRbM/iefwR1eTOLnkv5DvN61Frc7pzxPcuJNojkhIgrsFDlWFwHnA9Wms8tn8S7Fpj2JcGptWbmkwN0kmGHrQRTcHeV05OmdONWNeoPjKXp/0bpYGr/omS5XDU/i7xfxLUjvcIlCuh6598lnRx++/xdcbysvpm6dnVY8nE1GezWSMXczaYmqxLYEKIZJLh1IKyrGNflnQ8D+2FOMJ3oZeHPEnWOcceaybJGmu3xfsn37AvFBOs3iOeIkVo3GqMsiqDXAK4vyZKEYH6f58SsIt6fCGeKotdg74dp4VuZ1jsNQAx2TaCNLix06Nr+cSMqjVVYq5w8//MCtUU80ijNFRq98DdANChoh36dqHIJb98D3L/kaFtw2uHlWSw7RotV6WZfxaVoinpV1DNmP0nkUPVPGtz+8GyzPobp3xMuCHfEUsn/l42SIIl7KSlfLRVRYrYU1UVTymqmszAzHidKyHRsUiJv8xy7T3waKGrPFz/gdlnECuzqK5DHGtBC4VnEjMDOFFXhVdyvgU1lR185rknElbuu/mszj+L6bKiM38hsfJXNPWZEBCV/iClIZiB45Lc7N5KYuKtfv0ACYr1FgftxDa4RkCLw8OvPBfXk8ePBoZnpygWHW3sFRvzD+7gGNzenLCO4cbBYFdpTYfRa5GJYVZVU9wQuIoeMoijnwjQ40Rt43wAls8oXCPuUbQTa4u4YDOV+jzOyQnQJc4JW240SsQRZJ4G7pBpXICvShqMy4g0u8eM5eB/ABLqsAi/zMCnE3kc49FDv0QKMhYSiBpbJyjAo8uqD89xW60jce1NKMS749F+RdwlUTgZFDmTlPMHbAb1AtJbBUVrUjJbCkoUPrK6upg/GR5rOT8cFP3Wlw651tOHEZd9lm2uDhjSBlVZ+XJQyul5ZFu6FrJwG13DUE9tyQgvHaqiH9BlwjgT2uoyK9NMuE8SQ/UtXV8OAjKfJBZJdoKHf4iLPpgnJz9xgnDEMBbANSkgQeoLIel0Q7SbaPh1kyInU/wbOLBZ6m6G3lY7z9koXDuJFvkuhkWigDhX+k1l+foS3wO689hUjavF5XqaTs2NExdMiD68v1oDUAmSe4urgCXBEulsnbJh7QygzKQC1P34BMAwDx4o4lvsgf4fY1T7IwWeJsg+gbIC6Fg/+yAo+t0I6TwMcNMg9sGNj2NT+lTC8gGza6ZJjj4RyyHSUjN/FeNe5L8B6k+A/RqE9MGrWS10UGrSTpCA9ed8CROPPWbJUlSDIs60+6dZYTTGUrKnuTwjygcMNkKQzuJ2XuUzbgoxRQNOIRrOgViHczMqBcbb82n0FuPVFs4Mj5GleAKx34WYsrNbeSTrv3DHlxeyzlFdxLXI1gOr9xkyXXVUjBE/nDxp7liYYJ76QIc1l/KN+NkoEWfjkbrIXwkAjmCwVChYNSnxlmxxP9bJW8RQFTSLzqeLAzGx7kcSqQSW2/Ni/gnS49raAucBnwWAfANFWH9ECkgcoi7gjyiLIdsUNBOy/kMYdGhx2Mqu8cEMwG56b4TZhLyKm3D8YSWMBYH71J5UQveAUBv6GQeMGy30BYvEYUvpCH0sAUD+KGx968qLJLY9aBsLIogYDEsyIY1CE7NtO6o6KJ5ZzFA/bRXyK+m5VmGKc9NqcSZYXFjKRMc/wF5TkAfJiDM0M8lxAOTcYxMS006AsqZ46rEgDuFJV3DTgfwo+zNf4HD30gdUwQIefCBm1CrwpY1EVPmE+gg1Od20AHo0mjrK0CO/ectmRFD7Qu8rw8KmtkRdUACQzMDcBbXHSHMlIh25w8ii+4vJ9TSXGFJvRiWCoq7gfxs+Y/QJq38YwmH13SY11iVprPRpNFv4w407aRx0PyiNY8GE38cjikSc9MUkpFa+4zdJSHcI8mT4qY4hrgKi2gQl7bEvc1eQRFHYAHXtJAq0blPpQi+ISDleQyik+StaSFck6FjEU6OAMLrSNjlJanVEZEsoEH6AjG6Z1itKzWipSdz/1Y16+i3adoFWPTA/KL2QOr3ByRfFkcRzZKR4eXNEMmvIrCyyKANaT3y8oT9f88TfsBXUo0SDbkIJ3o6XmS7iE80RWTQfm6AA7ECKuAT/AYrkaV/0RvBFbHtDFIGr0x83yf93a/jQX+28dtyBFfFaA8swKOYmlFyw9v7bWmBsfaDEHsClcniyitLyYfR1lpcRys3BTW1aZDjkkY/XNiDPll8mtEKAcY9d9LJ8UTTON0gsfnMmmL2MwquAjxI1BgAOsN1MYbQTlf+mKAx6/wzKR/fP3p9Ye91g0UBjOmUNh8RWXWQQtHtbQxSYdPZ1zxRH4eRqe+1ZrLGq0rGj6HG5MU0BzPY8nhAtztlMI1eZybDrcqcLu76QIslZXWlb1XOtHD81i5OR5I2ZNA2Tr22F6m5I2zV96ISUc3czlSJ2aQSvrd149GOI1wqaAFyhmj5fy3O+hAjqnsOCbnQqe0alLxKScROaEIgpxI5ITiKIf4SrRaGQhXIuUPEzlodZDpWeG7o0h5FAuWC27AikvDvisJlb/IdbkDLvkGDWYOfmxzmdkixnjKZerFz/xXPE11nRkbKlz4nwF8QBxNmCNtqEkXJfGERx4Z2m610QZgH/2GAZR2wEO/H3z62TjvOByldFObrGHtxlD0PnDBvzjw8xonYugKAXkML7KL4izvlJVCQsNgj8YF3iAGsPmnotblo77kn/zYlEPhRDa47BXVbGE/a2wTdx6QOem/RIPJPFoVCjv86quvvgU8x2NZjdB472y6PLSmH8/15Xm33pV0JTta2tvv/9X729c/GZp8+nKFSM4DrTM2uwyRB9uwJMwBdGSzXi8h7grDM5uSNO7VDBeXuSMDBe4lAaX3aFTic1ClNH3AQSnoNQSmtFAe4/OdoFzHGuXKY4GvoQ11Y6eUhY7QCUxdJ++Wk0d7i0s3dzevSLp4HDu62B+WcSIk6rqLnIuMTgSYpzrPRsc98hghnR1oaQG8rejnykMyV3XaICYVZA0cjXOKD/KOdY0tSb/qe1UeaY+7ZI9lkkxuJMuCSrzA8yAZZ3jPzSChIY4VOKxbd6/1/grWNMtiW9G0QBqWdUYw6mIAfp7govIGuCJcM1yvXGWsOk7IrrQwA48ra/y5yhqzwF4KjfYZnruwtgH+eTFEiKc1eMWZS9ce/iPJcn8NFYlnDRmdSWxIX1fY0hWWFvXD3uJ6zYoay6A0hY0zKOMf9f0GdEvp6KBb9zzUQmUto5DrpAkLK/muTQQejVwkG8utkYNxR6GhdS9peSr+9/+6qt71vceKiljMb1sPjnyPYfNy8xXvsXO+xxIMxUHaAMbrrPeAtzUCbu0JvIEjlG+SUcYIcWNM9hzCBaG7JAoc58ProPvrK/imt8IXJ5Pqo6hkrd3Za324ZCeywmjNH6BQ45JYDNOKynx2zrKmhcuxB+NcTktXE0ov0rRdn9mp+J4DUB/gunTlrST8jfpiHGUA6zrC3ynvPYVcr6r2ysolEFit5xBEl8LAPcfJL303YhdBo8KuY/5c6GTghrDww4x4qyi1RHNTk3FqZhnw9b0jg22KmTSqjvRZ/xir5q4+1NoNhhAGUMxrKOgxrh4vVMQA7scVx55VV0pWfnSBEb/sSLLSHeN6jvgr6PxCXp0Vlcy2P3i1UivlL+sBw6Yj0J55oD/EMG2SR6e2yqq2WlEhO1nMU4GpzFlpVcY5vkdZxGqgOoMiuMJ0WtWPn7IsBF0vAPYWf/fvj3rrZcIsd26qUAobmmHeQXOHHSc0tfi1VVZYVI4DMhX1roh+xwoJsvW59dUZ8JuodbeqsdTbt/w+7mYFKiyHLJxngOJNDbjnDrQDnUWNadVWWcFgN2ZS8x+kNztrYHc7aXH7bHME0O7rNv3XuRycS1EbaQ65VqoUd57geYb7Ca4hlPRzKrh0u+Pd3uAEsbrcBhJG0pudJTibBJPeH2rD+3IDhKzzsyFfCg42/fdAOPRJnDP/UB5u8umQLv5nvLKWSVzzhRJSKXl5C7VVVgpRTShpC7vuWWFMds21DDom+mhIquE7clI1+uIJcgxdc9W9UIH2xdUFLr9Qafne7MQ1vzLxa+sGQ4gvBQUPBTClgqgetBSFRQOaemL+sSc6VZLpuWYWryaAzgBXR0Ovi/Z2iRUG5zOENXk4J3lRVroXvHyOH6EEIUqn6+nq9B6ijk/rSsJkxStr5CTiohUkHzfjvt1x2dGkVgouUFadkq6IAgrbwzvdtVVYKzeYSomCPcfVR2kDuILLQqNx0aWY42GK+3NXFxUK+zRrLy8tDi7xwHylRkp4QFlfQgYDz6TnDx8+DL3QxHKIFzoVE3n//m0XWU5Ns00oqikq4Xn6/xX+V954sSHkG6dtShCCGAHnVIIXK5XruItrjfESBidcXOmR95ima4cSywFyucQ9Oy8vAR3hiY9jWsgMjlRZeGGqYiI2u5lYr55OPOGRRKOKi6zNzkhZ0SDpVgy0FO8n0tIeqbHd/dQKY5KTDci2k8g6wn3uaQ0JuNxbj42Es5Tx9H9ufiYJu6SsJsakQIYcZh1Il1UKaHlJFo9ZLRWVTFIprnyOZ21KDv6XWxeBO8CVVFSSC3A5fTZDVeoR6LBzcgmzOh2J41KQNeH62lBBC91fUxkysxUpKxs6sHnZhlJf+SpiSvFPryCtpGnUABHWn81Q3gMVNsJlHGhRuW2tTr25cSHWiIB67iL7ojo24ZDLR7UJImUFty9cOeZMm1IaV1JG+KoCqajSwMq+pFsrRUjCUWGhcJycGOOaJ9M09xHSuIm7UVSNkIqS0NkFRTAm6aBn1QZM8jCBLZwNVgrmhWkU/hmYC00Y9AB7akEj+OGHHwbAO7PAbSnLOILCn5EOyv0YdHq4knKM8DzF9RoKHuK/zBCBeFBmBmXQXtzuz03owiB0TeCLYH3TK8qvKL1QWUHAmytA64oG3KnKzWNetuMO1bFYKWssdFVO0nCiE9Oz/scnLXDMQGCNvyZE02Ne0L7mqDdv3ILWzJSYOt6HBqKrcEnj3EeHLHGDA5Wpl794CcYLsQIijnnFwi7Ipf7Jy2/P1J/NVQ4X5uurUFZj5VrN1O1J7Qm4BJVk2+H9BTzUCzfqsi+fJzN2za/BX4cE8JGodWTrkueiZW7VHL93c49dKP/re5E5EdzBB/jjnGRGD6CwI016YZLEshYSaQDqLYGPx6TgUO0NCov2g5em7Kphx8QUTwMvpoUdbM81dOKkZ/GNzb9EWWc2hOuA49jTRnUogz8e2uKG5y9Pa0qR6Xg1zonbXON7l3+MV7XfIcqg3c+IS0cF6QiTZ4myRiYEi2AdFaiI/Eo6e1oKfSVS/mDcs8tJVw+JrXsbUx5MEVnzqraPunZMbDfD6mtJn6NEWf28+QE+qDjKVdFz5TGVn/SwIMetZmcWeLVFWbrCFpM21RdoMX/wySMn2aPuqGgzB95PTPefC43C3IGnVqGyqilnp0xiBjEAt+4xYxqm/+xpIUijymevWnWnYlouG/jF/nKjhg1qZTg4fPw873OQUiZYd9wJBnhThWU7t/10icT9lsDkFrNQWRXmSS4FYQJ7Hh9rTcLsVsCwM+hEqLDLyqr7iQErhTN4UBNNEwOUqkEjV6saM0yFRXs7xDM9K9arNrB9AoAvnIRawJxE1WZ0uPzQ1CgHXRTdFkEBSK0TDaTwKbjcU8ZTcKU+cnodH9E6hYXvZWQUYkZvbOr+ZNCpdVSdD/q2eSVOIuzE5pgnUEruU+8CL8L9HPevcT+BIs0ktIpg1I4/booIFGyE/5euikpaYmUlsKXCsldjj+VFGOTDNbDykhsmfL3T6spXVfh1/IQGJpXGP/3m7agqGWxiPkbKygKqhV2uKXWKCkzXgq97beP4r6jsdU//x799doz6eVETPjfuGzfrkJuxspJJ5VYc4/YJri7jEmGO+wmPOtk1i5WQgbdbdI48zKtH9w1EI0ycTHx1fngp/QI0B96YtSCErwTM9j757Mh1Uski641DsVLWdCnZoKCcHa6h+mpI6Tx27Vl1iFSmfqrs7AxPMKwIU/FWj9/99lPJ92qtaBcjLSb7n3w2bBS1WFKE8KKssqwaKBMJoAO8BHxaUe9IwNLmfm3sDkh4Aws7ACg7hsoC+D/76e/eOa8yVMZwDTJqlLUGlZBmgbPWmJm+Ssennrk08Xkqzvrxb1//pLvfel/BR5axR3mxP/zid/81sWa2BoixN4l66pEdeJZT3LNOZmWx1yhrWZJ1oIuGIBpLooF4/9jyd18/Gn382ly741CETFRa0weffjbeVLdXc+BesrxcEprgOve9DCjdFJFkprkvWQKo6KDkLHLJc/kE48iDxcfNBFEuoDhh+bZPuH/bOqDbu4mKyvkDvquKzvEGxR7g0nVkPFP7mLDodEeA9RYay+pNlP4IFY1X45zKsKwx7fhfrcly1r+HK8AlCEsFnWJK5NX+J48mZSgoZDQAI4/jjg0KEuH5tc/ZctBrqYk+Dkm6fLYIPJPLy9laa1FWuhOYOQ7igvs6uDumt+n/qiHSFdYFr2NWXUZxGnc/8ZT8vUW7e5t1mBhecn/wvhV9/h9voxjH9786NoWyybNu3JU09nFAugdFjYvvRWErU1ZV8GNw/wxXEJci8R/h3umg7QStjb+Fwl6jELm9ORqktxP7N0VYkMkIvJ4K+Q0x2TMUwmaCoWO4gpx7mYnmkdzS+NQc7UeMSsastBTqkwYUdPBj9it3jOdB2/T1ByspO/hQ8NaIF8uxSWJVbUKqqCya0zEqzM+jopKfPr0C3tiG0i0rCn0B5gYWDDr3jEV50trzqFBMuT9JVwzGQlPEvUZa6HtWr4ivZLpy++4s7Lr5SfJW1b3yym6QX8c0T8jrwKb+0G7f2ORXwF8E63pQAJObXKqyosAj5GzSG64wCmUpzdVTvD1HhoUNAIp7hj3O42Z31kr1VPaAuhogM3b6xoF1x1ckTRBd8ivKx2VSsDQ3GAWmNbBWVBYagn7h+xs57KXBG8eD5K1QUckHOo1juOfWn9UgjSY4SeCJAzbboWl4bIoghccrmtZlkRzyLeVjBY6Khka+EmfzgJ7oBfCcBuZxvsqdusKzTQUShx/aPdgUC6ssxGPURRDLoA6ufcyL9B/88x1UKfgKHPB6KxGyBxscGWW7trekXYqy0hpaCimrwH3Ssxl3pIm9e/eOL57bKGpMiif8U9kP44g6/qeXN1INvYcO8BQwUyjB0Idc6ygDR54CR/xcdJf2V4qyojH0crm1SFD0QgvUOxS13/b4LsL+pkuLhYmC0JSE4uE58Hq4Ogn8Oe6pPC9dj5QBbxegNUjQzrxlZ4rrGvC1Ohggk1m3yMgN3Tt2st6NiJc1Zg2MuCgGdqaHWUEqia9wakKIngEs2RV4oFXu40pXGJ/7UJ5LwkGBuib0Y1jgcsgwiJ8F/8zXOj8BfV8gMwdCUwfcWqGWoqywEI89l/JnLvQ4VgV+34VGCjeQKhTh4Blc05KlaGQ+KjhjBaLVBu5xJlF9ZAf1RSWvbQB/57bMQfYvLXDnFjgiFJRlKgLMACpFWdFovBYWBXSilzxvKUMGtlH9IkQ1k01r2imCTaUT3khheRBciob4kR0ElV2MUDGgGleHptlSMSxPK5ma5iWFh6wjKWwarhRlRSbfpjNyeUYB/+mCD/ez54Jviwu+L4BrqqhxdsQjfmHwMaEHGT0rzGiNANjRdYLsZwYszHj+lwH8HSjq7fXdg+cbdCCvbEmWpayRLUNZeCigSSVlkfAeB54e64gqt7SngxGkLSeziuDQuLpFMIL0ngBmbSBcLivYgpnkzWnj/MOHD0MQmycJerqfu0wglqKsEOrEU+FIZo4ecuqRni9S2g7Eo6UqnBjzpKyBL8GURYcKi1n4Q9Af4sqSP9vdkDAua+EK97yEcjjRLGXphoXFxEqIwg5cCwwLBnLh3IUOJhl45MapC400LhTkn+m41HMv9Wz72LVFrApPbTbpI78nuAJcMc8R7md0KzGmnvha01XLZiFolxZgcM6wps6hQeApkxn4HrnQKsWykiEoxxh/TkpGfO7JJT2XUMaX69gBFPAUFKSLk4smfzwNEyIxQwqQSopOecQ3pRB1gYsKGysqoQJcffC3PGUBsBdqZp5ptQ7KQDwFk65tmOUkjSFvXEJpyqp60RMn5vb2vBwQrgQ/ceElhRtZzjKmyPh59KSsUxNuuCSldnPRY+kIcZevShJXCL9WMFjCGRg4wjV3YIS4XjaelKasLJxyV2x6FBZw6FMhYAmdxgssTyK8TNyXfgsvRdtY2DFCYacujJisRyplu0J+XYs8O8AxWpayyMMbSkJhZ6ZEWSdwpw8UDVP0e/DtezElRKjKpZtUWLksIMY4PMTaWDhFrIMP8jAogitI59jjsACGnxm5AUxQBCdJR36F9URXGUpNBTIOlDlmKY8kiGp8ynwK67KAXi0+VlbA40oy6nSAiFNcAS5dYNs9R72FOiDTtFImmNJMKMU7xHY4bqnjJEQPV4ArDhFupuzdfVrTmHj8j17uBK4bG5ltQ5sDdxjTK/ifIn1QACNJnkiAKDfI9wzyPZbAJ2DmUFZpmVoeXoaIs+6A1ws8HMURdf9XyhfS+ID3HuTG3V+Pcc928S2uCB3m1NdEWloe7XTEtj87WAZWiHjs4WLpUnXApYgwFZf7aOg9GJWJmy/Qod7kZm6RAHrezz62YGMjUEods9ZRApxsQuOnGzvGxcYqCRPTsQctHd1LCXENDF3uUJN+LwnwQ+T7FAnRvcTViNC0TPv7+/1VEu5PsETP3KnsBoVK3OA6ihKNegQre5Z3BhN4jnBNcXHsMcO/cYDSDOEiXQOxY4zsMN2vdslMOOxAvo9xdZk/3TVc1mueUCyeVUVSPgN5HPokuK20vEt+kwUF5eXL5YGtcmaVneMbxF/h6mSl58TNES92uXNoeI9GWRbeiYIgOgGrQ83K4KXONHfODdZVhnKRZzoY0zSl+EfAk9IlXO0U1bTcJvDJA99N8HYNdmfd4CorWinsISzTAPlyNryfkf8Eca8AG2akNVGNBFqNslbYCJQihswyuYWwzOWqCovXZFWyBDZuzMrlA7pN3O+rthGWLKKGfCwBeAY3uA/iZ1//6MQ2rh36KrsJnY2xrGgoIxTsGa4AExItTARxlxC32Z24vCNoIqwGdjnuDjzLQTqWt8427uA33YOpfY+mZmg5m9rV1FaI3nmoSd+ZJLUZg50a5RXLLML9jMs2fLHa1iNRO9AuQctbAE+lfXVB8fsCzAYJhrm+fGIrgwSdym9rr6wQOA8LPy6SDDcC7LKFhZfRhQwoq16BrOZI59rxqAAuM9mzK8zTH0o5NF15YqeZhcAaNryzw7K2Bebk6Rxd66UbdbZQoaJSCmik7EF3MqjND9LPE3YgJB7yza8L8N4oYHugNw8GdVbK94PU5F2eorK88b5ko7KvG7jWyoqG0TMQUEDlNoDfClBaVDT6CxTGSPFogbEp39ilVdsozzwIL/TxweMsPmA1OQzQBpZ/09pLrZUV0g60Ek8l7triurKMVDgjRY3Fxgar3MU4SvSP4cYJAEMRcDYQ91qTRikBw4FAQnjT2kutlRWNaS4R+q7CcF8zyh44lv+5jTvMCT3UDxXOtI7GwPVyAohjuTcOvdZLN+ghpwYSnfuYmqdrxLdL4Epx03ovkX+E+xmu2uwygnyeJ/izvV1+UBrIZ6YE6MZC0UP1jusA+J0cGlToCWQ6rmhSh/XUy+HlLppr9XcPG3CzCbPBookTNFzjj+Ym64fWRTW642R8zn3EiRYfnUMO/cJodirg4aYQUADAThGu7ZEAVAvC8TMAeAUEpGeEa1a1nJRsrsFCh3xkBdf2kkWz7LhaW1YWXrlaV7jNFTzSePr6mPA2gYrKw7+QFxuaJHBjBs8Roks3kiD4huF4Czx4IWtQbm1+kMUMALysg1obTdZDhPHtxGRdlNYbdUMX/QWuTgYzTu0lg14lUbW3rJSC6rE545msxKWAaBX4mQSTylwiqp9YUbNoJ+E092tRWI8nUSyLBkVbW1tQdXAMRujWZykXeQxN3WhlYU+B28MV4OLGkJdlzUKDfqlhbRVkU6qMXneienMbckscdASXuOlbEwAi3NHKjybZFmXVdcQZdbI8L2pXN7/U3g1OVpiqpEkyzuVeNXgnRWX+6PFp9Q9ceDHFRZ5zU5w8eHoneWllxie8mo4wH25m4Ddsd3K3Wq2XboQVaA2GBk+3y0fgx5Kdld6EEeVRRCY4GtiZJq20JLUpQ6qod3xAYTfmZP87pj3c7LSyQn7eFAwN6ImH+jAiAYs4MULIAeY4LieptGh6Nci3Z5kBJwSPLXE3Fm1nlZWNxWetQXECn/QktJDnOeDmEtg8GLrAruP+PNq6eA9ejS+vSMdmrdJ2Vll914KDlbBmhUsUyHdsTQCKDmUdOuC7oPZckIHLj2J1HWlsFHqjrBtVXfeZVcsQ4f2Uwhha5KOKdhRlMdPJijSJwyy8Mw2T/NYN2yirvxqY+SNlRglu7BAW1mRjfIQcduoERTOJ1hN6o5ZufIrw/fv33DLok2TkQiyxhvwlXFMuUdDyfYtLtJZMC4tNABOUiZsABriyAt3m803dFJBVoF2Ka+9SYdNlxZjnBnFBOt7yeQgLF5rigocBcKhgAa68YHzWFCfQ8LXxDpQ3gILO2Dmt0eW9Vy6U+xqR3XsJBhHYhvi57c41g2xqA7qzlpU1QCsDK/bCQ23weJKJCR21IYCbKfoCPCrcJRq4+KypqjfPC8qwAoLyvITsXZTVaL/wSuYb+uDVD9w0GfDwMPAc4XIN56Y9PF8cQKYSRU3yNoDCUsErCbTOvNix+M5QyX5uSxceA5etdirstBvMmkbjZ+9Ol8w28EtvhybIGJ+KDoHT0Czt5QEqp1oDTXckEfhhvqGGL6MkNU6/NEICMCyy0+uQpvnVBX7nlZUVAYUd4M/GYs3g/h6ZWFVf76FCobx/zAlyGEEOp7h0wbjMOmIWshcPBXT5bmLaTrvBcYXRWsCtOsJzFMcJ/nmOkJGikibGas8FtAtBfNGJMxIqKsG7yoWPUZ3+KXtYyqcgEgkI0bIPBXBbCdIoq6pWTshA+Q6hBCeIijS1zeWRIzQaq3doQT/tXmqyyk/yRYc5QFE5FCiyqElmukq5k3HW93ybirIHASriBFeEaxmgyFPWCT0JyHykonfyr3GDc6qd7mr69DsfM6xo5IucLI2j0Xi91B944hBgYMjAHPl/bojTgDtIwEtlO+S/c6g+lRXCO4TCzFyFCJ7egEbHlA49DB8dmGm+uwrfuMEbXPNwDeee2DdWVOaL/Hue8m/ICCTQKKtASHUFqdOOpLrKaJv4apS1+tp0dlsVy77oWEsAkz9r58Ga+Q1EbJS14krDzOZLH1n6oqN4mdjwhFMlpzZ4DY6dBBpltZObNZbrNjuV8VzRseYjiQgLadOBYC9IOE/Sae7LlUCjrOXK9x511cC5lusSTnwqisWpkXOsi7qWwaX8O4nbKOsaqh3LLSGy5WUTuN0utEHU4UD5hkiXuMO0psY7t3R5N2kyCezLwBoo3xL461//+uoXv/jFP0H3f0ppcycPFPW3UngTuNls9hY8/d+f//zn/wm8Lq5OBn4Ipf5fv//976OMtCaqZAm0S6bfkC+QQOITD4McUFqyKr/AtmQDGyWosLz4Aawpv7jm0/VeZtL8GEmgUVYjcZUHPMA7ozjNoYvJHl7LY11gSSv/Alt5JWwou0rg/wNp6JZe9MHFaAAAAABJRU5ErkJggg== - mediatype: image/png - install: - spec: - deployments: - - name: 3scale-operator - spec: - replicas: 1 - selector: - matchLabels: - name: threescale-operator - strategy: {} - template: - metadata: - labels: - name: threescale-operator - spec: - containers: - - command: - - 3scale-operator - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: threescale-operator - - name: TAG - value: 1.0 - image: quay.io/3scale/3scale-operator:v0.3.0 - imagePullPolicy: Always - name: 3scale-operator - resources: {} - serviceAccountName: 3scale-operator - permissions: - - rules: - - apiGroups: - - '' - resources: - - pods - - replicationcontrollers - - services - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - - serviceaccounts - - bindings/finalizers - verbs: - - '*' - - apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' - - apiGroups: - - apps - resourceNames: - - 3scale-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - - rolebindings - verbs: - - '*' - - apiGroups: - - image.openshift.io - resources: - - imagestreams - - imagestreams/layers - verbs: - - '*' - - apiGroups: - - route.openshift.io - resources: - - routes - verbs: - - '*' - - apiGroups: - - route.openshift.io - resources: - - routes/custom-host - verbs: - - create - - apiGroups: - - route.openshift.io - resources: - - routes/status - verbs: - - get - - apiGroups: - - apps.openshift.io - resources: - - deploymentconfigs - verbs: - - '*' - - apiGroups: - - monitoring.coreos.com - resources: - - servicemonitors - verbs: - - get - - create - - apiGroups: - - apps.3scale.net - resources: - - '*' - verbs: - - '*' - - apiGroups: - - capabilities.3scale.net - resources: - - '*' - - bindings - - metrics - - plans - - limits - - mappingrules - - tenants - verbs: - - '*' - serviceAccountName: 3scale-operator - strategy: deployment - installModes: - - supported: true - type: OwnNamespace - - supported: true - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: false - type: AllNamespaces - keywords: - - 3scale - - API - links: - - name: GitHub - url: https://github.com/3scale/3scale-operator - - name: Documentation - url: https://github.com/3scale/3scale-operator/blob/v0.3.0/doc/user-guide.md - maintainers: - - email: openshift-operators@redhat.com - name: Red Hat - maturity: alpha - provider: - name: Red Hat - version: 0.3.0 diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apimanagers.apps.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apimanagers.apps.3scale.net.crd.yaml deleted file mode 100644 index ce59e0b62..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apimanagers.apps.3scale.net.crd.yaml +++ /dev/null @@ -1,165 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: apimanagers.apps.3scale.net -spec: - group: apps.3scale.net - names: - kind: APIManager - listKind: APIManagerList - plural: apimanagers - singular: apimanager - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - apicast: - properties: - image: - type: string - managementAPI: - type: string - openSSLVerify: - type: boolean - registryURL: - type: string - responseCodes: - type: boolean - type: object - appLabel: - type: string - backend: - properties: - image: - type: string - redisImage: - type: string - type: object - highAvailability: - properties: - enabled: - type: boolean - type: object - imageStreamTagImportInsecure: - type: boolean - resourceRequirementsEnabled: - type: boolean - system: - properties: - database: - properties: - mysql: - description: Union type. Only one of the fields can be set - properties: - image: - type: string - type: object - postgresql: - properties: - image: - type: string - type: object - type: object - fileStorage: - properties: - amazonSimpleStorageService: - properties: - awsBucket: - type: string - awsCredentialsSecret: - properties: - name: - type: string - type: object - awsRegion: - type: string - required: - - awsBucket - - awsRegion - - awsCredentialsSecret - type: object - persistentVolumeClaim: - description: Union type. Only one of the fields can be set. - properties: - storageClassName: - type: string - type: object - type: object - image: - type: string - memcachedImage: - type: string - redisImage: - type: string - type: object - tenantName: - type: string - wildcardDomain: - type: string - zync: - properties: - image: - type: string - postgreSQLImage: - type: string - type: object - required: - - wildcardDomain - type: object - status: - properties: - conditions: - items: - properties: - status: - type: string - type: - type: string - required: - - type - - status - type: object - type: array - deployments: - properties: - ready: - description: Deployments are ready to serve requests - items: - type: string - type: array - starting: - description: Deployments are starting, may or may not succeed - items: - type: string - type: array - stopped: - description: Deployments are not starting, unclear what next step - will be - items: - type: string - type: array - type: object - required: - - deployments - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apis.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apis.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 3d97b6b22..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/apis.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,363 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: apis.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: API - listKind: APIList - plural: apis - singular: api - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - description: - type: string - integrationMethod: - properties: - apicastHosted: - properties: - apiTestGetRequest: - type: string - authenticationSettings: - properties: - credentials: - properties: - apiKey: - properties: - authParameterName: - type: string - credentialsLocation: - type: string - required: - - authParameterName - - credentialsLocation - type: object - appID: - properties: - appIDParameterName: - type: string - appKeyParameterName: - type: string - credentialsLocation: - type: string - required: - - appIDParameterName - - appKeyParameterName - - credentialsLocation - type: object - openIDConnector: - properties: - credentialsLocation: - type: string - issuer: - type: string - required: - - issuer - - credentialsLocation - type: object - type: object - errors: - properties: - authenticationFailed: - properties: - contentType: - type: string - responseBody: - type: string - responseCode: - format: int64 - type: integer - required: - - responseCode - - contentType - - responseBody - type: object - authenticationMissing: - properties: - contentType: - type: string - responseBody: - type: string - responseCode: - format: int64 - type: integer - required: - - responseCode - - contentType - - responseBody - type: object - required: - - authenticationFailed - - authenticationMissing - type: object - hostHeader: - type: string - secretToken: - type: string - required: - - hostHeader - - secretToken - - credentials - - errors - type: object - mappingRulesSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - policiesSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - privateBaseURL: - type: string - required: - - privateBaseURL - - apiTestGetRequest - - authenticationSettings - type: object - apicastOnPrem: - properties: - apiTestGetRequest: - type: string - authenticationSettings: - properties: - credentials: - properties: - apiKey: - properties: - authParameterName: - type: string - credentialsLocation: - type: string - required: - - authParameterName - - credentialsLocation - type: object - appID: - properties: - appIDParameterName: - type: string - appKeyParameterName: - type: string - credentialsLocation: - type: string - required: - - appIDParameterName - - appKeyParameterName - - credentialsLocation - type: object - openIDConnector: - properties: - credentialsLocation: - type: string - issuer: - type: string - required: - - issuer - - credentialsLocation - type: object - type: object - errors: - properties: - authenticationFailed: - properties: - contentType: - type: string - responseBody: - type: string - responseCode: - format: int64 - type: integer - required: - - responseCode - - contentType - - responseBody - type: object - authenticationMissing: - properties: - contentType: - type: string - responseBody: - type: string - responseCode: - format: int64 - type: integer - required: - - responseCode - - contentType - - responseBody - type: object - required: - - authenticationFailed - - authenticationMissing - type: object - hostHeader: - type: string - secretToken: - type: string - required: - - hostHeader - - secretToken - - credentials - - errors - type: object - mappingRulesSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - policiesSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - privateBaseURL: - type: string - productionPublicBaseURL: - type: string - stagingPublicBaseURL: - type: string - required: - - privateBaseURL - - apiTestGetRequest - - authenticationSettings - - stagingPublicBaseURL - - productionPublicBaseURL - type: object - codePlugin: - properties: - authenticationSettings: - properties: - credentials: - properties: - apiKey: - properties: - authParameterName: - type: string - credentialsLocation: - type: string - required: - - authParameterName - - credentialsLocation - type: object - appID: - properties: - appIDParameterName: - type: string - appKeyParameterName: - type: string - credentialsLocation: - type: string - required: - - appIDParameterName - - appKeyParameterName - - credentialsLocation - type: object - openIDConnector: - properties: - credentialsLocation: - type: string - issuer: - type: string - required: - - issuer - - credentialsLocation - type: object - type: object - required: - - credentials - type: object - required: - - authenticationSettings - type: object - type: object - metricSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - planSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - required: - - description - - integrationMethod - type: object - status: - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/bindings.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/bindings.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 7d450b488..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/bindings.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,65 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: bindings.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: Binding - listKind: BindingList - plural: bindings - singular: binding - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - apiSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - credentialsRef: - properties: - name: - type: string - namespace: - type: string - type: object - required: - - credentialsRef - type: object - status: - properties: - currentState: - type: string - desiredState: - type: string - lastSync: - properties: - nanos: - type: integer - seconds: - type: integer - type: object - previousState: - type: string - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/limits.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/limits.capabilities.3scale.net.crd.yaml deleted file mode 100644 index cc2e18c09..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/limits.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: limits.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: Limit - listKind: LimitList - plural: limits - singular: limit - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - maxValue: - format: int64 - type: integer - metricRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - resourceVersion: - type: string - uid: - type: string - type: object - period: - type: string - required: - - period - - maxValue - - metricRef - type: object - status: - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/mappingrules.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/mappingrules.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 7c59a4128..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/mappingrules.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: mappingrules.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: MappingRule - listKind: MappingRuleList - plural: mappingrules - singular: mappingrule - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - increment: - format: int64 - type: integer - method: - type: string - metricRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - resourceVersion: - type: string - uid: - type: string - type: object - path: - type: string - required: - - path - - method - - increment - - metricRef - type: object - status: - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/metrics.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/metrics.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 47a7de6c1..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/metrics.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: metrics.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: Metric - listKind: MetricList - plural: metrics - singular: metric - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - description: - type: string - incrementHits: - type: boolean - unit: - type: string - required: - - unit - - description - - incrementHits - type: object - status: - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/plans.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/plans.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 9456a8d0b..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/plans.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: plans.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: Plan - listKind: PlanList - plural: plans - singular: plan - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - approvalRequired: - type: boolean - costs: - properties: - costMonth: - format: double - type: number - setupFee: - format: double - type: number - type: object - default: - type: boolean - limitSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - type: array - type: array - matchLabels: - type: object - type: object - trialPeriod: - format: int64 - type: integer - required: - - default - - trialPeriod - - approvalRequired - - limitSelector - type: object - status: - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/tenants.capabilities.3scale.net.crd.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/tenants.capabilities.3scale.net.crd.yaml deleted file mode 100644 index 836d7005d..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/0.3.0/tenants.capabilities.3scale.net.crd.yaml +++ /dev/null @@ -1,74 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: tenants.capabilities.3scale.net -spec: - group: capabilities.3scale.net - names: - kind: Tenant - listKind: TenantList - plural: tenants - singular: tenant - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - email: - type: string - masterCredentialsRef: - properties: - name: - type: string - namespace: - type: string - type: object - organizationName: - type: string - passwordCredentialsRef: - properties: - name: - type: string - namespace: - type: string - type: object - systemMasterUrl: - type: string - tenantSecretRef: - properties: - name: - type: string - namespace: - type: string - type: object - username: - type: string - required: - - username - - email - - organizationName - - systemMasterUrl - - tenantSecretRef - - passwordCredentialsRef - - masterCredentialsRef - type: object - status: - properties: - adminId: - format: int64 - type: integer - tenantId: - format: int64 - type: integer - required: - - tenantId - - adminId - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/3scale-community-operator.package.yaml b/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/3scale-community-operator.package.yaml deleted file mode 100644 index 7726bb10a..000000000 --- a/pkg/sqlite/testdata/incorrectbundle/3scale-community-operator/3scale-community-operator.package.yaml +++ /dev/null @@ -1,5 +0,0 @@ -packageName: 3scale-community-operator -channels: -- currentCSV: 3scale-community-operator.v0.3.0 - name: alpha - diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdcluster.crd.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdcluster.crd.yaml deleted file mode 100644 index 6f2068711..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdcluster.crd.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: etcdclusters.etcd.database.coreos.com -spec: - group: etcd.database.coreos.com - version: v1beta2 - scope: Namespaced - names: - plural: etcdclusters - singular: etcdcluster - kind: EtcdCluster - listKind: EtcdClusterList - shortNames: - - etcdclus - - etcd diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdoperator.clusterserviceversion.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdoperator.clusterserviceversion.yaml deleted file mode 100644 index c1b4b75fc..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.6.1/etcdoperator.clusterserviceversion.yaml +++ /dev/null @@ -1,174 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: etcdoperator.v0.6.1 - namespace: placeholder - annotations: - tectonic-visibility: ocs -spec: - 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. - A 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. - - _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ - - ### Reading and writing to etcd - - Communicate with etcd though its command line utility `etcdctl` or with the API using the Kubernetes Service. - - [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) - - ### Supported Features - **High availability** - Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. - **Automated updates** - Rolling 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. - **Backups included** - Coming soon, the ability to schedule backups to happen on or off cluster. - keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] - version: 0.6.1 - maturity: alpha - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - labels: - alm-status-descriptors: etcdoperator.v0.6.1 - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - selector: - matchLabels: - 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 - - icon: - - base64data: iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC - mediatype: image/png - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: etcd-operator - rules: - - apiGroups: - - etcd.database.coreos.com - resources: - - etcdclusters - verbs: - - "*" - - apiGroups: - - storage.k8s.io - resources: - - storageclasses - verbs: - - "*" - - apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - verbs: - - "*" - - apiGroups: - - apps - resources: - - deployments - verbs: - - "*" - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - deployments: - - name: etcd-operator - spec: - replicas: 1 - selector: - matchLabels: - name: etcd-operator-alm-owned - template: - metadata: - name: etcd-operator-alm-owned - labels: - name: etcd-operator-alm-owned - spec: - serviceAccountName: etcd-operator - containers: - - name: etcd-operator - command: - - etcd-operator - - --create-crd=false - image: quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943 - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - customresourcedefinitions: - owned: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - 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' - 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: service - 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' diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdbackup.crd.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdbackup.crd.yaml deleted file mode 100644 index 5fa9e2ef0..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdbackup.crd.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: etcdbackups.etcd.database.coreos.com -spec: - group: etcd.database.coreos.com - version: v1beta2 - scope: Namespaced - names: - kind: EtcdBackup - listKind: EtcdBackupList - plural: etcdbackups - singular: etcdbackup diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdcluster.crd.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdcluster.crd.yaml deleted file mode 100644 index 6f2068711..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdcluster.crd.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: etcdclusters.etcd.database.coreos.com -spec: - group: etcd.database.coreos.com - version: v1beta2 - scope: Namespaced - names: - plural: etcdclusters - singular: etcdcluster - kind: EtcdCluster - listKind: EtcdClusterList - shortNames: - - etcdclus - - etcd diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.0.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.0.yaml deleted file mode 100644 index b30f229ad..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.0.yaml +++ /dev/null @@ -1,281 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: etcdoperator.v0.9.0 - namespace: placeholder - annotations: - tectonic-visibility: ocs - 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":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' -spec: - 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. - A 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. - - _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ - - ### Reading and writing to etcd - - Communicate with etcd though its command line utility `etcdctl` or with the API using the Kubernetes Service. - - [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. - - - **Automated updates** - - - Rolling 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. - - - **Backups included** - - - Coming soon, the ability to schedule backups to happen on or off cluster. - keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] - version: 0.9.0 - maturity: alpha - replaces: etcdoperator.v0.6.1 - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - labels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - selector: - matchLabels: - 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 - - icon: - - base64data: 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 - mediatype: image/png - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: etcd-operator - 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 - deployments: - - name: etcd-operator - spec: - replicas: 1 - selector: - matchLabels: - name: etcd-operator-alm-owned - template: - metadata: - name: etcd-operator-alm-owned - labels: - name: etcd-operator-alm-owned - spec: - serviceAccountName: etcd-operator - containers: - - name: etcd-operator - command: - - etcd-operator - - --create-crd=false - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-backup-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - 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 - - name: etcd-restore-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - 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 - customresourcedefinitions: - owned: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - 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' - - name: etcdbackups.etcd.database.coreos.com - version: v1beta2 - kind: EtcdBackup - displayName: etcd Backup - description: Represents the intent to backup an etcd cluster. - 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' - - 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. - 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' diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml deleted file mode 100644 index 93ad8515f..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml +++ /dev/null @@ -1,306 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: etcdoperator.v0.9.2 - namespace: placeholder - annotations: - tectonic-visibility: ocs - olm.skipRange: "< 0.6.0" - 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":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' -spec: - relatedImages: - - name: etcd-v3.4.0 - image: quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84 - - name: etcd-3.4.1 - image: quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f - 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. - A 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. - - _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ - - ### Reading and writing to etcd - - Communicate with etcd though its command line utility `etcdctl` or with the API using the Kubernetes Service. - - [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. - - - **Automated updates** - - - Rolling 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. - - - **Backups included** - - - Coming soon, the ability to schedule backups to happen on or off cluster. - keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] - version: 0.9.2 - maturity: alpha - replaces: etcdoperator.v0.9.0 - skips: - - etcdoperator.v0.9.1 - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - labels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - selector: - matchLabels: - 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 - - icon: - - base64data: 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 - mediatype: image/png - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: etcd-operator - 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 - deployments: - - name: etcd-operator - spec: - replicas: 1 - selector: - matchLabels: - name: etcd-operator-alm-owned - template: - metadata: - name: etcd-operator-alm-owned - labels: - name: etcd-operator-alm-owned - spec: - serviceAccountName: etcd-operator - containers: - - name: etcd-operator - command: - - etcd-operator - - --create-crd=false - image: quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2 - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-backup-operator - image: quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2 - 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 - - name: etcd-restore-operator - image: quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2 - 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 - customresourcedefinitions: - required: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - 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' - owned: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - 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' - - name: etcdbackups.etcd.database.coreos.com - version: v1beta2 - kind: EtcdBackup - displayName: etcd Backup - description: Represents the intent to backup an etcd cluster. - 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' - - 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. - 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' diff --git a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdrestore.crd.yaml b/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdrestore.crd.yaml deleted file mode 100644 index 8e28bb20a..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/0.9.2/etcdrestore.crd.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: etcdrestores.etcd.database.coreos.com -spec: - group: etcd.database.coreos.com - version: v1beta2 - scope: Namespaced - names: - kind: EtcdRestore - listKind: EtcdRestoreList - plural: etcdrestores - singular: etcdrestore diff --git a/pkg/sqlite/testdata/loader_data/etcd/etcd.package.yaml b/pkg/sqlite/testdata/loader_data/etcd/etcd.package.yaml deleted file mode 100644 index b594fafd1..000000000 --- a/pkg/sqlite/testdata/loader_data/etcd/etcd.package.yaml +++ /dev/null @@ -1,9 +0,0 @@ -packageName: etcd -channels: -- name: alpha - currentCSV: etcdoperator.v0.9.2 -- name: beta - currentCSV: etcdoperator.v0.9.0 -- name: stable - currentCSV: etcdoperator.v0.9.2 -defaultChannel: alpha diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/alertmanager.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheus.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusoperator.0.14.0.clusterserviceversion.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusoperator.0.14.0.clusterserviceversion.yaml deleted file mode 100644 index e78634047..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusoperator.0.14.0.clusterserviceversion.yaml +++ /dev/null @@ -1,240 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.14.0 - namespace: placeholder -spec: - displayName: Prometheus - description: | - An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach. - - _The Prometheus Open Cloud Service is Public Alpha. The goal before Beta is for additional user testing and minor bug fixes._ - - ### Monitoring applications - - Prometheus scrapes your application metrics based on targets maintained in a ServiceMonitor object. When alerts need to be sent, they are processsed by an AlertManager. - - [Read the complete guide to monitoring applications with the Prometheus Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/prometheus-ocs.html) - - ## Supported Features - - **High availability** - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - **Updates via automated operations** - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - **Handles the dynamic nature of containers** - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator Source Code - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.14.0 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-14-0 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: ["get", "list"] - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - servicemonitors - verbs: - - "*" - - apiGroups: - - apps - resources: - - statefulsets - verbs: ["*"] - - apiGroups: [""] - resources: - - configmaps - - secrets - verbs: ["*"] - - apiGroups: [""] - resources: - - pods - verbs: ["list", "delete"] - - apiGroups: [""] - resources: - - services - - endpoints - verbs: ["get", "create", "update"] - - apiGroups: [""] - resources: - - nodes - verbs: ["list", "watch"] - - apiGroups: [""] - resources: - - namespaces - verbs: ['list'] - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-14-0 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731 - command: - - sh - - -c - - > - /bin/operator --namespace=$K8S_NAMESPACE --crd-apigroup monitoring.coreos.com - --labels alm-status-descriptors=prometheusoperator.0.14.0,alm-owner-prometheus=prometheusoperator - --kubelet-service=kube-system/kubelet - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - maturity: alpha - version: 0.14.0 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Define resources requests and limits for single Pods - displayName: Resource Request - path: resources.requests - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The current number of Pods for the cluster - displayName: Cluster Size - path: replicas - - path: prometheusSelector - displayName: Prometheus Service Selector - description: Label selector to find the service that routes to this prometheus - x-descriptors: - - 'urn:alm:descriptor:label:selector' - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Selector to select which namespaces the Endpoints objects are discovered from - displayName: Monitoring Namespaces - path: namespaceSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:namespaceSelector' - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alert Manager - description: Configures an Alert Manager for the namespace - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusrule.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/servicemonitor.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.14.0/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/alertmanager.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheus.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusoperator.0.15.0.clusterserviceversion.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusoperator.0.15.0.clusterserviceversion.yaml deleted file mode 100644 index f2f8d54c0..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusoperator.0.15.0.clusterserviceversion.yaml +++ /dev/null @@ -1,264 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.15.0 - namespace: placeholder - annotations: - tectonic-visibility: ocs - alm-examples: '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v1.7.0","serviceAccountName":"prometheus-k8s","serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"resources":{"requests":{"memory":"400Mi"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus","prometheus":"k8s"}},"namespaceSelector":{"matchNames":["monitoring"]},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3}}]' -spec: - replaces: prometheusoperator.0.14.0 - displayName: Prometheus - description: | - An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach. - - _The Prometheus Open Cloud Service is Public Alpha. The goal before Beta is for additional user testing and minor bug fixes._ - - ### Monitoring applications - - Prometheus scrapes your application metrics based on targets maintained in a ServiceMonitor object. When alerts need to be sent, they are processsed by an AlertManager. - - [Read the complete guide to monitoring applications with the Prometheus Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/prometheus-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - - - **Updates via automated operations** - - - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - - - **Handles the dynamic nature of containers** - - - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator Source Code - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.15.0 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-14-0 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: ["get", "list"] - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - servicemonitors - verbs: - - "*" - - apiGroups: - - apps - resources: - - statefulsets - verbs: ["*"] - - apiGroups: [""] - resources: - - configmaps - - secrets - verbs: ["*"] - - apiGroups: [""] - resources: - - pods - verbs: ["list", "delete"] - - apiGroups: [""] - resources: - - services - - endpoints - verbs: ["get", "create", "update"] - - apiGroups: [""] - resources: - - nodes - verbs: ["list", "watch"] - - apiGroups: [""] - resources: - - namespaces - verbs: ['list'] - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-14-0 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d - command: - - sh - - -c - - > - /bin/operator --namespace=$K8S_NAMESPACE --crd-apigroup monitoring.coreos.com - --labels alm-status-descriptors=prometheusoperator.0.15.0,alm-owner-prometheus=prometheusoperator - --kubelet-service=kube-system/kubelet - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - maturity: alpha - version: 0.15.0 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The current number of Pods for the cluster - displayName: Cluster Size - path: replicas - - path: prometheusSelector - displayName: Prometheus Service Selector - description: Label selector to find the service that routes to this prometheus - x-descriptors: - - 'urn:alm:descriptor:label:selector' - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Selector to select which namespaces the Endpoints objects are discovered from - displayName: Monitoring Namespaces - path: namespaceSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:namespaceSelector' - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alert Manager - description: Configures an Alert Manager for the namespace - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - 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: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusrule.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/servicemonitor.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.15.0/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/alertmanager.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheus.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml deleted file mode 100644 index fb1a9e99e..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml +++ /dev/null @@ -1,271 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.22.2 - namespace: placeholder - annotations: - alm-examples: '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v2.3.2","serviceAccountName":"prometheus-k8s","securityContext": {}, "serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus"}},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3, "securityContext": {}}}]' -spec: - replaces: prometheusoperator.0.15.0 - displayName: Prometheus Operator - description: | - The Prometheus Operator for Kubernetes provides easy monitoring definitions for Kubernetes services and deployment and management of Prometheus instances. - - Once installed, the Prometheus Operator provides the following features: - - * **Create/Destroy**: Easily launch a Prometheus instance for your Kubernetes namespace, a specific application or team easily using the Operator. - - * **Simple Configuration**: Configure the fundamentals of Prometheus like versions, persistence, retention policies, and replicas from a native Kubernetes resource. - - * **Target Services via Labels**: Automatically generate monitoring target configurations based on familiar Kubernetes label queries; no need to learn a Prometheus specific configuration language. - - ### Other Supported Features - - **High availability** - - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - - **Updates via automated operations** - - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - - **Handles the dynamic nature of containers** - - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: Red Hat - email: openshift-operators@redhat.com - - provider: - name: Red Hat - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.22.2 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-22-2 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - '*' - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - prometheuses/finalizers - - alertmanagers/finalizers - - servicemonitors - - prometheusrules - verbs: - - '*' - - apiGroups: - - apps - resources: - - statefulsets - verbs: - - '*' - - apiGroups: - - "" - resources: - - configmaps - - secrets - verbs: - - '*' - - apiGroups: - - "" - resources: - - pods - verbs: - - list - - delete - - apiGroups: - - "" - resources: - - services - - endpoints - verbs: - - get - - create - - update - - apiGroups: - - "" - resources: - - nodes - verbs: - - list - - watch - - apiGroups: - - "" - resources: - - namespaces - verbs: - - list - - watch - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-22-2 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf - args: - - -namespace=$(K8S_NAMESPACE) - - -manage-crds=false - - -logtostderr=true - - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - - --prometheus-config-reloader=quay.io/coreos/prometheus-config-reloader:v0.22.2 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - nodeSelector: - beta.kubernetes.io/os: linux - maturity: beta - version: 0.22.2 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - - name: prometheusrules.monitoring.coreos.com - version: v1 - kind: PrometheusRule - displayName: Prometheus Rule - description: A Prometheus Rule configures groups of sequentially evaluated recording and alerting rules. - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alertmanager - description: Configures an Alertmanager for the namespace - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - 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: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusrule.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/servicemonitor.crd.yaml b/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/0.22.2/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/loader_data/prometheus/prometheus.package.yaml b/pkg/sqlite/testdata/loader_data/prometheus/prometheus.package.yaml deleted file mode 100644 index d20e25450..000000000 --- a/pkg/sqlite/testdata/loader_data/prometheus/prometheus.package.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packageName: prometheus -channels: -- name: preview - currentCSV: prometheusoperator.0.22.2 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/alertmanager.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheus.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusoperator.0.14.0.clusterserviceversion.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusoperator.0.14.0.clusterserviceversion.yaml deleted file mode 100644 index e78634047..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusoperator.0.14.0.clusterserviceversion.yaml +++ /dev/null @@ -1,240 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.14.0 - namespace: placeholder -spec: - displayName: Prometheus - description: | - An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach. - - _The Prometheus Open Cloud Service is Public Alpha. The goal before Beta is for additional user testing and minor bug fixes._ - - ### Monitoring applications - - Prometheus scrapes your application metrics based on targets maintained in a ServiceMonitor object. When alerts need to be sent, they are processsed by an AlertManager. - - [Read the complete guide to monitoring applications with the Prometheus Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/prometheus-ocs.html) - - ## Supported Features - - **High availability** - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - **Updates via automated operations** - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - **Handles the dynamic nature of containers** - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator Source Code - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.14.0 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-14-0 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: ["get", "list"] - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - servicemonitors - verbs: - - "*" - - apiGroups: - - apps - resources: - - statefulsets - verbs: ["*"] - - apiGroups: [""] - resources: - - configmaps - - secrets - verbs: ["*"] - - apiGroups: [""] - resources: - - pods - verbs: ["list", "delete"] - - apiGroups: [""] - resources: - - services - - endpoints - verbs: ["get", "create", "update"] - - apiGroups: [""] - resources: - - nodes - verbs: ["list", "watch"] - - apiGroups: [""] - resources: - - namespaces - verbs: ['list'] - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-14-0 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731 - command: - - sh - - -c - - > - /bin/operator --namespace=$K8S_NAMESPACE --crd-apigroup monitoring.coreos.com - --labels alm-status-descriptors=prometheusoperator.0.14.0,alm-owner-prometheus=prometheusoperator - --kubelet-service=kube-system/kubelet - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - maturity: alpha - version: 0.14.0 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Define resources requests and limits for single Pods - displayName: Resource Request - path: resources.requests - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The current number of Pods for the cluster - displayName: Cluster Size - path: replicas - - path: prometheusSelector - displayName: Prometheus Service Selector - description: Label selector to find the service that routes to this prometheus - x-descriptors: - - 'urn:alm:descriptor:label:selector' - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Selector to select which namespaces the Endpoints objects are discovered from - displayName: Monitoring Namespaces - path: namespaceSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:namespaceSelector' - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alert Manager - description: Configures an Alert Manager for the namespace - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusrule.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/servicemonitor.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/manifests/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/metadata/annotations.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/metadata/annotations.yaml deleted file mode 100644 index 2fba0f4bf..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.14.0/metadata/annotations.yaml +++ /dev/null @@ -1,4 +0,0 @@ -annotations: - operators.operatorframework.io.bundle.package.v1: "prometheus" - operators.operatorframework.io.bundle.channels.v1: "preview" - operators.operatorframework.io.bundle.channel.default.v1: "preview" \ No newline at end of file diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/alertmanager.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheus.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusoperator.0.15.0.clusterserviceversion.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusoperator.0.15.0.clusterserviceversion.yaml deleted file mode 100644 index f2f8d54c0..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusoperator.0.15.0.clusterserviceversion.yaml +++ /dev/null @@ -1,264 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.15.0 - namespace: placeholder - annotations: - tectonic-visibility: ocs - alm-examples: '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v1.7.0","serviceAccountName":"prometheus-k8s","serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"resources":{"requests":{"memory":"400Mi"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus","prometheus":"k8s"}},"namespaceSelector":{"matchNames":["monitoring"]},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3}}]' -spec: - replaces: prometheusoperator.0.14.0 - displayName: Prometheus - description: | - An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach. - - _The Prometheus Open Cloud Service is Public Alpha. The goal before Beta is for additional user testing and minor bug fixes._ - - ### Monitoring applications - - Prometheus scrapes your application metrics based on targets maintained in a ServiceMonitor object. When alerts need to be sent, they are processsed by an AlertManager. - - [Read the complete guide to monitoring applications with the Prometheus Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/prometheus-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - - - **Updates via automated operations** - - - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - - - **Handles the dynamic nature of containers** - - - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator Source Code - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.15.0 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-14-0 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: ["get", "list"] - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - servicemonitors - verbs: - - "*" - - apiGroups: - - apps - resources: - - statefulsets - verbs: ["*"] - - apiGroups: [""] - resources: - - configmaps - - secrets - verbs: ["*"] - - apiGroups: [""] - resources: - - pods - verbs: ["list", "delete"] - - apiGroups: [""] - resources: - - services - - endpoints - verbs: ["get", "create", "update"] - - apiGroups: [""] - resources: - - nodes - verbs: ["list", "watch"] - - apiGroups: [""] - resources: - - namespaces - verbs: ['list'] - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-14-0 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d - command: - - sh - - -c - - > - /bin/operator --namespace=$K8S_NAMESPACE --crd-apigroup monitoring.coreos.com - --labels alm-status-descriptors=prometheusoperator.0.15.0,alm-owner-prometheus=prometheusoperator - --kubelet-service=kube-system/kubelet - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - maturity: alpha - version: 0.15.0 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The current number of Pods for the cluster - displayName: Cluster Size - path: replicas - - path: prometheusSelector - displayName: Prometheus Service Selector - description: Label selector to find the service that routes to this prometheus - x-descriptors: - - 'urn:alm:descriptor:label:selector' - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: Selector to select which namespaces the Endpoints objects are discovered from - displayName: Monitoring Namespaces - path: namespaceSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:namespaceSelector' - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alert Manager - description: Configures an Alert Manager for the namespace - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - 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: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusrule.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/servicemonitor.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/manifests/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/metadata/annotations.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/metadata/annotations.yaml deleted file mode 100644 index b2367a90a..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.15.0/metadata/annotations.yaml +++ /dev/null @@ -1,4 +0,0 @@ -annotations: - operators.operatorframework.io.bundle.package.v1: "prometheus" - operators.operatorframework.io.bundle.channels.v1: "preview,stable" - operators.operatorframework.io.bundle.channel.default.v1: "preview" diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/alertmanager.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/alertmanager.crd.yaml deleted file mode 100644 index ce56f4bb6..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/alertmanager.crd.yaml +++ /dev/null @@ -1,2398 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Alertmanager - plural: alertmanagers - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - baseImage: - description: Base image that is used to deploy pods, without tag. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logLevel: - description: Log level for Alertmanager to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlaying managed objects - are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Alertmanager object, which shall be mounted into the Alertmanager - Pods. The Secrets are mounted into /etc/alertmanager/secrets/. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Alertmanager container image to be deployed. Defaults - to the value of `version`. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version the cluster should be on. - type: string - status: - description: 'Most recent observed status of the Alertmanager cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - cluster. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Alertmanager - cluster that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheus.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheus.crd.yaml deleted file mode 100644 index 1a02408aa..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheus.crd.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheuses.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: Prometheus - plural: prometheuses - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: 'Specification of the desired behavior of the Prometheus cluster. - More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - additionalAlertManagerConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - additionalScrapeConfigs: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must be a valid - secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must be defined - type: boolean - required: - - key - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Node affinity is a group of node affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches all - objects with implicit weight 0 (i.e. it's a no-op). A null - preferred scheduling term matches no objects (i.e. is also - a no-op). - properties: - preference: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - preference - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: A node selector represents the union of the results - of one or more label queries over a set of nodes; that is, - it represents the OR of the selectors represented by the node - selector terms. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be - empty. If the operator is Gt or Lt, the values - array must have a single element, which will - be interpreted as an integer. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - type: array - required: - - nodeSelectorTerms - podAffinity: - description: Pod affinity is a group of inter pod affinity scheduling - rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the affinity expressions specified by this field, - but it may choose a node that violates one or more of the - expressions. The node that is most preferred is the one with - the greatest sum of weights, i.e. for each node that meets - all of the scheduling requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to the sum - if the node has pods which matches the corresponding podAffinityTerm; - the node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may not - try to eventually evict the pod from its node. When there - are multiple elements, the lists of nodes corresponding to - each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - podAntiAffinity: - description: Pod anti affinity is a group of inter pod anti affinity - scheduling rules. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes - that satisfy the anti-affinity expressions specified by this - field, but it may choose a node that violates one or more - of the expressions. The node that is most preferred is the - one with the greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource request, - requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field - and adding "weight" to the sum if the node has pods which - matches the corresponding podAffinityTerm; the node(s) with - the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located is - defined as running on a node whose value of the label - with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: A label selector is a label query over - a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector - matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey matches - that of any node on which any of the selected pods - is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - weight - - podAffinityTerm - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will not - be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms must - be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) that - this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of pods - is running - properties: - labelSelector: - description: A label selector is a label query over a - set of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of any - node on which any of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: array - alerting: - description: AlertingSpec defines parameters for alerting configuration - of Prometheus servers. - properties: - alertmanagers: - description: AlertmanagerEndpoints Prometheus should fire alerts - against. - items: - description: AlertmanagerEndpoints defines a selection of a single - Endpoints object containing alertmanager IPs to fire alerts - against. - properties: - bearerTokenFile: - description: BearerTokenFile to read from filesystem to use - when authenticating to Alertmanager. - type: string - name: - description: Name of Endpoints object in Namespace. - type: string - namespace: - description: Namespace of Endpoints object. - type: string - pathPrefix: - description: Prefix for the HTTP path alerts are pushed to. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use when firing alerts. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - required: - - namespace - - name - - port - type: array - required: - - alertmanagers - baseImage: - description: Base image to use for a Prometheus deployment. - type: string - containers: - description: Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to a Prometheus pod. - items: - description: A single application container that you want to run within - a pod. - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will be - unchanged. The $(VAR_NAME) syntax can be escaped with a double - $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - regardless of whether the variable exists or not. Cannot be - updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. The - docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable exists - or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be a - C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in the - container and any service environment variables. If a - variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. Defaults to "".' - type: string - valueFrom: - description: EnvVarSource represents a source for the value - of an EnvVar. - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap or it's - key must be defined - type: boolean - required: - - key - fieldRef: - description: ObjectFieldSelector selects an APIVersioned - field of an object. - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - resourceFieldRef: - description: ResourceFieldSelector represents container - resources (cpu, memory) and their output format - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: {} - resource: - description: 'Required: resource to select' - type: string - required: - - resource - secretKeyRef: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's - key must be defined - type: boolean - required: - - key - required: - - name - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be a - C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key will - take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set of - ConfigMaps - properties: - configMapRef: - description: |- - ConfigMapEnvSource selects a ConfigMap to populate the environment variables with. - The contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the ConfigMap must be defined - type: boolean - prefix: - description: An optional identifier to prepend to each key - in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: |- - SecretEnvSource selects a Secret to populate the environment variables with. - The contents of the target Secret's Data field will represent the key-value pairs as environment variables. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Lifecycle describes actions that the management system - should take in response to container lifecycle events. For the - PostStart and PreStop lifecycle handlers, management of the - container blocks until the action is complete, unless the container - process fails, in which case the handler is aborted. - properties: - postStart: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - preStop: - description: Handler defines a specific action that should - be taken - properties: - exec: - description: ExecAction describes a "run in container" - action. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - httpGet: - description: HTTPGetAction describes an action based on - HTTP Get requests. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - tcpSocket: - description: TCPSocketAction describes an action based - on opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - name: - description: Name of the container specified as a DNS_LABEL. Each - container in a pod must have a unique name (DNS_LABEL). Cannot - be updated. - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about the - network connections a container uses, but is primarily informational. - Not specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Cannot be updated. - items: - description: ContainerPort represents a network port in a single - container. - properties: - containerPort: - description: Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If specified, - this must be a valid port number, 0 < x < 65536. If HostNetwork - is specified, this must match ContainerPort. Most containers - do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod must - have a unique name. Name for the port that can be referred - to by services. - type: string - protocol: - description: Protocol for port. Must be UDP or TCP. Defaults - to "TCP". - type: string - required: - - containerPort - type: array - readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: ExecAction describes a "run in container" action. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit - status of 0 is treated as live/healthy and non-zero - is unhealthy. - items: - type: string - type: array - failureThreshold: - description: Minimum consecutive failures for the probe to - be considered failed after having succeeded. Defaults to - 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGetAction describes an action based on HTTP - Get requests. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header to - be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: string - - type: integer - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to - be considered successful after having failed. Defaults to - 1. Must be 1 for liveness. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocketAction describes an action based on - opening a socket - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: string - - type: integer - required: - - port - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - securityContext: - description: SecurityContext holds security configuration that - will be applied to a container. Some fields are present in both - SecurityContext and PodSecurityContext. When both are set, - the values in SecurityContext take precedence. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: Adds and removes POSIX capabilities from running - containers. - properties: - add: - description: Added capabilities - items: - type: string - type: array - drop: - description: Removed capabilities - items: - type: string - type: array - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. - type: boolean - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to - the container - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - stdin: - description: Whether this container should allocate a buffer for - stdin in the container runtime. If this is not set, reads from - stdin in the container will always result in EOF. Default is - false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the stdin - channel after it has been opened by a single attach. When stdin - is true the stdin stream will remain open across multiple attach - sessions. If stdinOnce is set to true, stdin is opened on container - start, is empty until the first client attaches to stdin, and - then remains open and accepts data until the client disconnects, - at which time stdin is closed and remains closed until the container - is restarted. If this flag is false, a container processes that - reads from stdin will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the container''s - termination message will be written is mounted into the container''s - filesystem. Message written is intended to be brief final status, - such as an assertion failure message. Will be truncated by the - node if greater than 4096 bytes. The total message length across - all containers will be limited to 12kb. Defaults to /dev/termination-log. - Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be populated. - File will use the contents of terminationMessagePath to populate - the container status message on both success and failure. FallbackToLogsOnError - will use the last chunk of container log output if the termination - message file is empty and the container exited with an error. - The log output is limited to 2048 bytes or 80 lines, whichever - is smaller. Defaults to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. This is an alpha feature and may change - in the future. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - name - - devicePath - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other way - around. When not set, MountPropagationHostToContainer - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - required: - - name - - mountPath - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might be - configured in the container image. Cannot be updated. - type: string - required: - - name - type: array - evaluationInterval: - description: Interval between consecutive evaluations. - type: string - externalLabels: - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same namespace - to use for pulling prometheus and alertmanager images from registries - see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to let - you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logLevel: - description: Log level for Prometheus to be configured with. - type: string - nodeSelector: - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: When a Prometheus deployment is paused, no actions except - for deletion will be performed on the underlying objects. - type: boolean - podMetadata: - description: ObjectMeta is metadata that all persisted resources must - have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs to. - This is used to distinguish resources with same name and namespace - in different clusters. This field is not set anywhere right now - and apiserver is going to ignore it if set in create or update - request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to gracefully - terminate before it will be removed from the system. Only set - when deletionTimestamp is also set. May only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports correct - marshaling to YAML and JSON. Wrappers are provided for many of - the factory methods that the time package offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted from the - registry. Each entry is an identifier for the responsible component - that will remove the entry from the list. If the deletionTimestamp - of the object is non-nil, entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that must execute - in order before this object is visible. When the last pending - initializer is removed, and no failing result is set, the - initializers struct will be set to nil and the object is considered - as initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible for - initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that don't return - other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of - this representation of an object. Servers should convert - recognized schemas to the latest internal value, and may - reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this status, - 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional properties - that MAY be set by the server to provide additional information - about a response. The Reason field of a Status object - defines what attributes will be set. Clients must ignore - fields that do not match the defined type of each attribute, - and should assume that any attribute may be empty, invalid, - or under defined. - properties: - causes: - description: The Causes array includes more details - associated with the StatusReason failure. Not all - StatusReasons may provide detailed causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases when - multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description of the - cause of the error. This field may be presented - as-is to a reader. - type: string - reason: - description: A machine-readable description of - the cause of the error. If this value is empty - there is no information available. - type: string - type: array - group: - description: The group attribute of the resource associated - with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource associated - with the status StatusReason. On some operations may - differ from the requested resource Kind. More info: - https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource associated - with the status StatusReason (when there is a single - name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds before - the operation should be retried. Some errors may indicate - the client must take an alternate action - for those - errors this field may indicate how long to wait before - taking the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there is a - single resource which can be described). More info: - http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the status - of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various status - objects. A resource may have only one of {ObjectMeta, - ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of - available objects. Continuing a list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients - to determine when objects have changed. Value must - be treated as opaque by clients and passed unmodified - back to the server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why this - operation is in the "Failure" status. If this value is - empty there is no information available. A Reason clarifies - an HTTP status code but does not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a client - to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If ALL objects - in the list have been deleted, this object will be garbage collected. - If this object is managed by a controller, then an entry in this - list will point to this controller, with the controller field - set to true. There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information to let - you identify an owning object. Currently, an owning object must - be in the same namespace, so there is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. Populated - by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - remoteRead: - description: If specified, the remote_read spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteReadSpec defines the remote_read configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: bearer token for remote read. - type: string - bearerTokenFile: - description: File to read bearer token for remote read. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - readRecent: - description: Whether reads should be made for queries for time - ranges that the local storage should have complete data for. - type: boolean - remoteTimeout: - description: Timeout for requests to the remote read endpoint. - type: string - requiredMatchers: - description: An optional list of equality matchers which have - to be present in a selector to query the remote read endpoint. - type: object - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - required: - - url - type: array - remoteWrite: - description: If specified, the remote_write spec. This is an experimental - feature, it may change in any upcoming release in a breaking way. - items: - description: RemoteWriteSpec defines the remote_write configuration - for prometheus. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerToken: - description: File to read bearer token for remote write. - type: string - bearerTokenFile: - description: File to read bearer token for remote write. - type: string - proxyUrl: - description: Optional ProxyURL - type: string - queueConfig: - description: QueueConfig allows the tuning of remote_write queue_config - parameters. This object is referenced in the RemoteWriteSpec - object. - properties: - batchSendDeadline: - description: BatchSendDeadline is the maximum time a sample - will wait in buffer. - type: string - capacity: - description: Capacity is the number of samples to buffer per - shard before we start dropping them. - format: int32 - type: integer - maxBackoff: - description: MaxBackoff is the maximum retry delay. - type: string - maxRetries: - description: MaxRetries is the maximum number of times to - retry a batch on recoverable errors. - format: int32 - type: integer - maxSamplesPerSend: - description: MaxSamplesPerSend is the maximum number of samples - per send. - format: int32 - type: integer - maxShards: - description: MaxShards is the maximum number of shards, i.e. - amount of concurrency. - format: int32 - type: integer - minBackoff: - description: MinBackoff is the initial retry delay. Gets doubled - for every retry. - type: string - remoteTimeout: - description: Timeout for requests to the remote write endpoint. - type: string - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - url: - description: The URL of the endpoint to send samples to. - type: string - writeRelabelConfigs: - description: The list of remote write relabel configurations. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - required: - - url - type: array - replicas: - description: Number of instances to deploy for a Prometheus deployment. - format: int32 - type: integer - resources: - description: ResourceRequirements describes the compute resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute resources - required. If Requests is omitted for a container, it defaults - to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - retention: - description: Time duration Prometheus shall retain data for. - type: string - routePrefix: - description: The route prefix Prometheus registers HTTP handlers for. - This is useful, if using ExternalURL and a proxy is rewriting HTTP - routes of a request, and the actual ExternalURL is still true, but - the server serves requests under a different route prefix. For example - for use with `kubectl proxy`. - type: string - ruleNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - ruleSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - scrapeInterval: - description: Interval between consecutive scrapes. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as the - Prometheus object, which shall be mounted into the Prometheus Pods. - The Secrets are mounted into /etc/prometheus/secrets/. - Secrets changes after initial creation of a Prometheus object are - not reflected in the running Pods. To change the secrets mounted into - the Prometheus Pods, the object must be deleted and recreated with - the new list of secrets. - items: - type: string - type: array - securityContext: - description: PodSecurityContext holds pod-level security attributes - and common container settings. Some fields are also present in container.securityContext. Field - values of container.securityContext take precedence over field values - of PodSecurityContext. - properties: - fsGroup: - description: |- - A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: - 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- - If unset, the Kubelet will not modify the ownership and permissions of any volume. - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: SELinuxOptions are the labels to be applied to the - container - properties: - level: - description: Level is SELinux level label that applies to the - container. - type: string - role: - description: Role is a SELinux role label that applies to the - container. - type: string - type: - description: Type is a SELinux type label that applies to the - container. - type: string - user: - description: User is a SELinux user label that applies to the - container. - type: string - supplementalGroups: - description: A list of groups applied to the first process run in - each container, in addition to the container's primary GID. If - unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: array - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount to - use to run the Prometheus Pods. - type: string - serviceMonitorNamespaceSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - serviceMonitorSelector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - storage: - description: StorageSpec defines the configured storage for a group - Prometheus servers. - properties: - class: - description: 'Name of the StorageClass to use when requesting storage - provisioning. More info: https://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses - DEPRECATED' - type: string - emptyDir: - description: Represents an empty directory for a pod. Empty directory - volumes support ownership management and SELinux relabeling. - properties: - medium: - description: 'What type of storage medium should back this directory. - The default is "" which means to use the node''s default medium. - Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: {} - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - limits: - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the - key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - volumeClaimTemplate: - description: PersistentVolumeClaim is a user's request for and claim - to a persistent volume - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - description: ObjectMeta is metadata that all persisted resources - must have, which includes all objects users must create. - properties: - annotations: - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - clusterName: - description: The name of the cluster which the object belongs - to. This is used to distinguish resources with same name - and namespace in different clusters. This field is not - set anywhere right now and apiserver is going to ignore - it if set in create or update request. - type: string - creationTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - deletionGracePeriodSeconds: - description: Number of seconds allowed for this object to - gracefully terminate before it will be removed from the - system. Only set when deletionTimestamp is also set. May - only be shortened. Read-only. - format: int64 - type: integer - deletionTimestamp: - description: Time is a wrapper around time.Time which supports - correct marshaling to YAML and JSON. Wrappers are provided - for many of the factory methods that the time package - offers. - format: date-time - type: string - finalizers: - description: Must be empty before the object is deleted - from the registry. Each entry is an identifier for the - responsible component that will remove the entry from - the list. If the deletionTimestamp of the object is non-nil, - entries in this list can only be removed. - items: - type: string - type: array - generateName: - description: |- - GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. - If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). - Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency - type: string - generation: - description: A sequence number representing a specific generation - of the desired state. Populated by the system. Read-only. - format: int64 - type: integer - initializers: - description: Initializers tracks the progress of initialization. - properties: - pending: - description: Pending is a list of initializers that - must execute in order before this object is visible. - When the last pending initializer is removed, and - no failing result is set, the initializers struct - will be set to nil and the object is considered as - initialized and visible to all clients. - items: - description: Initializer is information about an initializer - that has not yet completed. - properties: - name: - description: name of the process that is responsible - for initializing this object. - type: string - required: - - name - type: array - result: - description: Status is a return value for calls that - don't return other objects. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - code: - description: Suggested HTTP return code for this - status, 0 if not set. - format: int32 - type: integer - details: - description: StatusDetails is a set of additional - properties that MAY be set by the server to provide - additional information about a response. The Reason - field of a Status object defines what attributes - will be set. Clients must ignore fields that do - not match the defined type of each attribute, - and should assume that any attribute may be empty, - invalid, or under defined. - properties: - causes: - description: The Causes array includes more - details associated with the StatusReason failure. - Not all StatusReasons may provide detailed - causes. - items: - description: StatusCause provides more information - about an api.Status failure, including cases - when multiple errors are encountered. - properties: - field: - description: |- - The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" - type: string - message: - description: A human-readable description - of the cause of the error. This field - may be presented as-is to a reader. - type: string - reason: - description: A machine-readable description - of the cause of the error. If this value - is empty there is no information available. - type: string - type: array - group: - description: The group attribute of the resource - associated with the status StatusReason. - type: string - kind: - description: 'The kind attribute of the resource - associated with the status StatusReason. On - some operations may differ from the requested - resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: The name attribute of the resource - associated with the status StatusReason (when - there is a single name which can be described). - type: string - retryAfterSeconds: - description: If specified, the time in seconds - before the operation should be retried. Some - errors may indicate the client must take an - alternate action - for those errors this field - may indicate how long to wait before taking - the alternate action. - format: int32 - type: integer - uid: - description: 'UID of the resource. (when there - is a single resource which can be described). - More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - message: - description: A human-readable description of the - status of this operation. - type: string - metadata: - description: ListMeta describes metadata that synthetic - resources must have, including lists and various - status objects. A resource may have only one of - {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user - set a limit on the number of items returned, - and indicates that the server has more data - available. The value is opaque and may be - used to issue another request to the endpoint - that served this list to retrieve the next - set of available objects. Continuing a list - may not be possible if the server configuration - has changed or more than a few minutes have - passed. The resourceVersion field returned - when using this continue value will be identical - to the value in the first response. - type: string - resourceVersion: - description: 'String that identifies the server''s - internal version of this object that can be - used by clients to determine when objects - have changed. Value must be treated as opaque - by clients and passed unmodified back to the - server. Populated by the system. Read-only. - More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: selfLink is a URL representing - this object. Populated by the system. Read-only. - type: string - reason: - description: A machine-readable description of why - this operation is in the "Failure" status. If - this value is empty there is no information available. - A Reason clarifies an HTTP status code but does - not override it. - type: string - status: - description: 'Status of the operation. One of: "Success" - or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' - type: string - required: - - pending - labels: - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is - required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be updated. - More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - namespace: - description: |- - Namespace defines the space within each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. - Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - type: string - ownerReferences: - description: List of objects depended by this object. If - ALL objects in the list have been deleted, this object - will be garbage collected. If this object is managed by - a controller, then an entry in this list will point to - this controller, with the controller field set to true. - There cannot be more than one managing controller. - items: - description: OwnerReference contains enough information - to let you identify an owning object. Currently, an - owning object must be in the same namespace, so there - is no namespace field. - properties: - apiVersion: - description: API version of the referent. - type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from - the key-value store until this reference is removed. - Defaults to false. To set this field, a user needs - "delete" permission of the owner, otherwise 422 - (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the - managing controller. - type: boolean - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' - type: string - required: - - apiVersion - - kind - - name - - uid - type: array - resourceVersion: - description: |- - An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. - Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency - type: string - selfLink: - description: SelfLink is a URL representing this object. - Populated by the system. Read-only. - type: string - uid: - description: |- - UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. - Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids - type: string - spec: - description: PersistentVolumeClaimSpec describes the common - attributes of storage devices and allows a Source for provider-specific - attributes - properties: - accessModes: - description: 'AccessModes contains the desired access modes - the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - resources: - description: ResourceRequirements describes the compute - resource requirements. - properties: - limits: - description: 'Limits describes the maximum amount of - compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - selector: - description: A label selector is a label query over a set - of resources. The result of matchLabels and matchExpressions - are ANDed. An empty label selector matches all objects. - A null label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - storageClassName: - description: 'Name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is required - by the claim. Value of Filesystem is implied when not - included in claim spec. This is an alpha feature and may - change in the future. - type: string - volumeName: - description: VolumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - status: - description: PersistentVolumeClaimStatus is the current status - of a persistent volume claim. - properties: - accessModes: - description: 'AccessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - capacity: - description: Represents the actual resources of the underlying - volume. - type: object - conditions: - description: Current Condition of persistent volume claim. - If underlying persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contails details - about state of pvc - properties: - lastProbeTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - lastTransitionTime: - description: Time is a wrapper around time.Time which - supports correct marshaling to YAML and JSON. Wrappers - are provided for many of the factory methods that - the time package offers. - format: date-time - type: string - message: - description: Human-readable message indicating details - about last transition. - type: string - reason: - description: Unique, this should be a short, machine - understandable string that gives the reason for - condition's last transition. If it reports "ResizeStarted" - that means the underlying persistent volume is being - resized. - type: string - status: - type: string - type: - type: string - required: - - type - - status - type: array - phase: - description: Phase represents the current phase of PersistentVolumeClaim. - type: string - tag: - description: Tag of Prometheus container image to be deployed. Defaults - to the value of `version`. - type: string - thanos: - description: ThanosSpec defines parameters for a Prometheus server within - a Thanos deployment. - properties: - baseImage: - description: Thanos base image if other than default. - type: string - gcs: - description: ThanosGCSSpec defines parameters for use of Google - Cloud Storage (GCS) with Thanos. - properties: - bucket: - description: Google Cloud Storage bucket name for stored blocks. - If empty it won't store any block inside Google Cloud Storage. - type: string - peers: - description: Peers is a DNS name for Thanos to discover peers through. - type: string - s3: - description: ThanosSpec defines parameters for of AWS Simple Storage - Service (S3) with Thanos. (S3 compatible services apply as well) - properties: - accessKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bucket: - description: S3-Compatible API bucket name for stored blocks. - type: string - endpoint: - description: S3-Compatible API endpoint for stored blocks. - type: string - insecure: - description: Whether to use an insecure connection with an S3-Compatible - API. - type: boolean - secretKey: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - signatureVersion2: - description: Whether to use S3 Signature Version 2; otherwise - Signature Version 4 will be used. - type: boolean - tag: - description: Tag of Thanos sidecar container image to be deployed. - Defaults to the value of `version`. - type: string - version: - description: Version describes the version of Thanos to use. - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, operator - must be Exists; this combination means to match all values and - all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. Exists - is equivalent to wildcard for value, so that a pod can tolerate - all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the - toleration (which must be of effect NoExecute, otherwise this - field is ignored) tolerates the taint. By default, it is not - set, which means tolerate the taint forever (do not evict). - Zero and negative values will be treated as 0 (evict immediately) - by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise - just a regular string. - type: string - type: array - version: - description: Version of Prometheus to be deployed. - type: string - status: - description: 'Most recent observed status of the Prometheus cluster. Read-only. - Not included when requesting from the apiserver, only from the Prometheus - Operator API itself. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Prometheus deployment. - format: int32 - type: integer - paused: - description: Represents whether any actions on the underlaying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Prometheus - deployment. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this Prometheus - deployment that have the desired version spec. - format: int32 - type: integer - required: - - paused - - replicas - - updatedReplicas - - availableReplicas - - unavailableReplicas - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusoperator.0.22.2.clusterserviceversion.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusoperator.0.22.2.clusterserviceversion.yaml deleted file mode 100644 index a40fc1b92..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusoperator.0.22.2.clusterserviceversion.yaml +++ /dev/null @@ -1,270 +0,0 @@ -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: prometheusoperator.0.22.2 - namespace: placeholder - annotations: - alm-examples: '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v2.3.2","serviceAccountName":"prometheus-k8s","securityContext": {}, "serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus"}},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3, "securityContext": {}}}]' -spec: - displayName: Prometheus Operator - description: | - The Prometheus Operator for Kubernetes provides easy monitoring definitions for Kubernetes services and deployment and management of Prometheus instances. - - Once installed, the Prometheus Operator provides the following features: - - * **Create/Destroy**: Easily launch a Prometheus instance for your Kubernetes namespace, a specific application or team easily using the Operator. - - * **Simple Configuration**: Configure the fundamentals of Prometheus like versions, persistence, retention policies, and replicas from a native Kubernetes resource. - - * **Target Services via Labels**: Automatically generate monitoring target configurations based on familiar Kubernetes label queries; no need to learn a Prometheus specific configuration language. - - ### Other Supported Features - - **High availability** - - Multiple instances are run across failure zones and data is replicated. This keeps your monitoring available during an outage, when you need it most. - - **Updates via automated operations** - - New Prometheus versions are deployed using a rolling update with no downtime, making it easy to stay up to date. - - **Handles the dynamic nature of containers** - - Alerting rules are attached to groups of containers instead of individual instances, which is ideal for the highly dynamic nature of container deployment. - - keywords: ['prometheus', 'monitoring', 'tsdb', 'alerting'] - - maintainers: - - name: Red Hat - email: openshift-operators@redhat.com - - provider: - name: Red Hat - - links: - - name: Prometheus - url: https://www.prometheus.io/ - - name: Documentation - url: https://coreos.com/operators/prometheus/docs/latest/ - - name: Prometheus Operator - url: https://github.com/coreos/prometheus-operator - - labels: - alm-status-descriptors: prometheusoperator.0.22.2 - alm-owner-prometheus: prometheusoperator - - selector: - matchLabels: - alm-owner-prometheus: prometheusoperator - - icon: - - base64data: PHN2ZyB3aWR0aD0iMjQ5MCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOC4wMDEuNjY3QzU3LjMxMS42NjcgMCA1Ny45NzEgMCAxMjguNjY0YzAgNzAuNjkgNTcuMzExIDEyNy45OTggMTI4LjAwMSAxMjcuOTk4UzI1NiAxOTkuMzU0IDI1NiAxMjguNjY0QzI1NiA1Ny45NyAxOTguNjg5LjY2NyAxMjguMDAxLjY2N3ptMCAyMzkuNTZjLTIwLjExMiAwLTM2LjQxOS0xMy40MzUtMzYuNDE5LTMwLjAwNGg3Mi44MzhjMCAxNi41NjYtMTYuMzA2IDMwLjAwNC0zNi40MTkgMzAuMDA0em02MC4xNTMtMzkuOTRINjcuODQyVjE3OC40N2gxMjAuMzE0djIxLjgxNmgtLjAwMnptLS40MzItMzMuMDQ1SDY4LjE4NWMtLjM5OC0uNDU4LS44MDQtLjkxLTEuMTg4LTEuMzc1LTEyLjMxNS0xNC45NTQtMTUuMjE2LTIyLjc2LTE4LjAzMi0zMC43MTYtLjA0OC0uMjYyIDE0LjkzMyAzLjA2IDI1LjU1NiA1LjQ1IDAgMCA1LjQ2NiAxLjI2NSAxMy40NTggMi43MjItNy42NzMtOC45OTQtMTIuMjMtMjAuNDI4LTEyLjIzLTMyLjExNiAwLTI1LjY1OCAxOS42OC00OC4wNzkgMTIuNTgtNjYuMjAxIDYuOTEuNTYyIDE0LjMgMTQuNTgzIDE0LjggMzYuNTA1IDcuMzQ2LTEwLjE1MiAxMC40Mi0yOC42OSAxMC40Mi00MC4wNTYgMC0xMS43NjkgNy43NTUtMjUuNDQgMTUuNTEyLTI1LjkwNy02LjkxNSAxMS4zOTYgMS43OSAyMS4xNjUgOS41MyA0NS40IDIuOTAyIDkuMTAzIDIuNTMyIDI0LjQyMyA0Ljc3MiAzNC4xMzguNzQ0LTIwLjE3OCA0LjIxMy00OS42MiAxNy4wMTQtNTkuNzg0LTUuNjQ3IDEyLjguODM2IDI4LjgxOCA1LjI3IDM2LjUxOCA3LjE1NCAxMi40MjQgMTEuNDkgMjEuODM2IDExLjQ5IDM5LjYzOCAwIDExLjkzNi00LjQwNyAyMy4xNzMtMTEuODQgMzEuOTU4IDguNDUyLTEuNTg2IDE0LjI4OS0zLjAxNiAxNC4yODktMy4wMTZsMjcuNDUtNS4zNTVjLjAwMi0uMDAyLTMuOTg3IDE2LjQwMS0xOS4zMTQgMzIuMTk3eiIgZmlsbD0iI0RBNEUzMSIvPjwvc3ZnPg== - mediatype: image/svg+xml - - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: prometheus-k8s - rules: - - apiGroups: [""] - resources: - - nodes - - services - - endpoints - - pods - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: - - configmaps - verbs: ["get"] - - serviceAccountName: prometheus-operator-0-22-2 - rules: - - apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - '*' - - apiGroups: - - monitoring.coreos.com - resources: - - alertmanagers - - prometheuses - - prometheuses/finalizers - - alertmanagers/finalizers - - servicemonitors - - prometheusrules - verbs: - - '*' - - apiGroups: - - apps - resources: - - statefulsets - verbs: - - '*' - - apiGroups: - - "" - resources: - - configmaps - - secrets - verbs: - - '*' - - apiGroups: - - "" - resources: - - pods - verbs: - - list - - delete - - apiGroups: - - "" - resources: - - services - - endpoints - verbs: - - get - - create - - update - - apiGroups: - - "" - resources: - - nodes - verbs: - - list - - watch - - apiGroups: - - "" - resources: - - namespaces - verbs: - - list - - watch - deployments: - - name: prometheus-operator - spec: - replicas: 1 - selector: - matchLabels: - k8s-app: prometheus-operator - template: - metadata: - labels: - k8s-app: prometheus-operator - spec: - serviceAccount: prometheus-operator-0-22-2 - containers: - - name: prometheus-operator - image: quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf - args: - - -namespace=$(K8S_NAMESPACE) - - -manage-crds=false - - -logtostderr=true - - --config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1 - - --prometheus-config-reloader=quay.io/coreos/prometheus-config-reloader:v0.22.2 - env: - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 8080 - name: http - resources: - limits: - cpu: 200m - memory: 100Mi - requests: - cpu: 100m - memory: 50Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - nodeSelector: - beta.kubernetes.io/os: linux - maturity: beta - version: 0.22.2 - customresourcedefinitions: - owned: - - name: prometheuses.monitoring.coreos.com - version: v1 - kind: Prometheus - displayName: Prometheus - description: A running Prometheus instance - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: A selector for the ConfigMaps from which to load rule files - displayName: Rule Config Map Selector - path: ruleSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap' - - description: ServiceMonitors to be selected for target discovery - displayName: Service Monitor Selector - path: serviceMonitorSelector - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:selector:monitoring.coreos.com:v1:ServiceMonitor' - - description: The ServiceAccount to use to run the Prometheus pods - displayName: Service Account - path: serviceAccountName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:ServiceAccount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - - name: prometheusrules.monitoring.coreos.com - version: v1 - kind: PrometheusRule - displayName: Prometheus Rule - description: A Prometheus Rule configures groups of sequentially evaluated recording and alerting rules. - - name: servicemonitors.monitoring.coreos.com - version: v1 - kind: ServiceMonitor - displayName: Service Monitor - description: Configures prometheus to monitor a particular k8s service - resources: - - kind: Pod - version: v1 - specDescriptors: - - description: The label to use to retrieve the job name from - displayName: Job Label - path: jobLabel - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:label' - - description: A list of endpoints allowed as part of this ServiceMonitor - displayName: Endpoints - path: endpoints - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:endpointList' - - name: alertmanagers.monitoring.coreos.com - version: v1 - kind: Alertmanager - displayName: Alertmanager - description: Configures an Alertmanager for the namespace - resources: - - kind: StatefulSet - version: v1beta2 - - kind: Pod - version: v1 - specDescriptors: - - description: Desired number of Pods for the cluster - displayName: Size - path: replicas - 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: resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusrule.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusrule.crd.yaml deleted file mode 100644 index 7ced5a680..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/prometheusrule.crd.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: prometheusrules.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: PrometheusRule - plural: prometheusrules - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: PrometheusRuleSpec contains specification parameters for a - Rule. - properties: - groups: - description: Content of Prometheus rule file - items: - description: RuleGroup is a list of sequentially evaluated recording - and alerting rules. - properties: - interval: - type: string - name: - type: string - rules: - items: - description: Rule describes an alerting or recording rule. - properties: - alert: - type: string - annotations: - type: object - expr: - type: string - for: - type: string - labels: - type: object - record: - type: string - required: - - expr - type: array - required: - - name - - rules - type: array - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/servicemonitor.crd.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/servicemonitor.crd.yaml deleted file mode 100644 index 029639684..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/manifests/servicemonitor.crd.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: servicemonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - kind: ServiceMonitor - plural: servicemonitors - scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - description: ServiceMonitorSpec contains specification parameters for a - ServiceMonitor. - properties: - endpoints: - description: A list of endpoints allowed as part of this ServiceMonitor. - items: - description: Endpoint defines a scrapeable endpoint serving Prometheus - metrics. - properties: - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints' - properties: - password: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - username: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - optional: - description: Specify whether the Secret or it's key must - be defined - type: boolean - required: - - key - bearerTokenFile: - description: File to read bearer token for scraping targets. - type: string - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - interval: - description: Interval at which metrics should be scraped - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It defines - ``-section of Prometheus configuration. - More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - description: Action to perform based on regex matching. - Default is 'replace' - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. defailt is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular expression - for the replace, keep, and drop actions. - items: - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: array - params: - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. - type: string - port: - description: Name of the service port this endpoint refers to. - Mutually exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - scheme: - description: HTTP scheme to use for scraping. - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended - type: string - targetPort: - anyOf: - - type: string - - type: integer - tlsConfig: - description: TLSConfig specifies TLS configuration parameters. - properties: - caFile: - description: The CA cert to use for the targets. - type: string - certFile: - description: The client cert file for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: The client key file for the targets. - type: string - serverName: - description: Used to verify the hostname for the targets. - type: string - type: array - jobLabel: - description: The label to use to retrieve the job name from. - type: string - namespaceSelector: - description: A selector for selecting namespaces either selecting all - namespaces or a list of namespaces. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names. - items: - type: string - type: array - selector: - description: A label selector is a label query over a set of resources. - The result of matchLabels and matchExpressions are ANDed. An empty - label selector matches all objects. A null label selector matches - no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains - values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to a - set of values. Valid operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator - is In or NotIn, the values array must be non-empty. If the - operator is Exists or DoesNotExist, the values array must - be empty. This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: array - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator is - "In", and the values array contains only "value". The requirements - are ANDed. - type: object - targetLabels: - description: TargetLabels transfers labels on the Kubernetes Service - onto the target. - items: - type: string - type: array - required: - - endpoints - - selector - version: v1 diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/annotations.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/annotations.yaml deleted file mode 100644 index 258fd57c5..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/annotations.yaml +++ /dev/null @@ -1,4 +0,0 @@ -annotations: - operators.operatorframework.io.bundle.package.v1: "prometheus" - operators.operatorframework.io.bundle.channels.v1: "preview,stable" - operators.operatorframework.io.bundle.channel.default.v1: "stable" \ No newline at end of file diff --git a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/dependencies.yaml b/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/dependencies.yaml deleted file mode 100644 index 516097592..000000000 --- a/pkg/sqlite/testdata/strandedbundles/prometheus.0.22.2/metadata/dependencies.yaml +++ /dev/null @@ -1,10 +0,0 @@ -dependencies: - - type: olm.package - value: - packageName: testoperator - version: "> 0.2.0" - - type: olm.gvk - value: - group: testprometheus.coreos.com - kind: testtestprometheus - version: v1 diff --git a/pkg/sqlite/testdata/test_db_migrations/invalid/randomscript.sql b/pkg/sqlite/testdata/test_db_migrations/invalid/randomscript.sql deleted file mode 100644 index 5f2200a3b..000000000 --- a/pkg/sqlite/testdata/test_db_migrations/invalid/randomscript.sql +++ /dev/null @@ -1 +0,0 @@ -Select * from users; \ No newline at end of file diff --git a/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.down.sql b/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.down.sql deleted file mode 100644 index b9e984a8c..000000000 --- a/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.down.sql +++ /dev/null @@ -1 +0,0 @@ -/* Initialize the database version */ \ No newline at end of file diff --git a/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.up.sql b/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.up.sql deleted file mode 100644 index b9e984a8c..000000000 --- a/pkg/sqlite/testdata/test_db_migrations/valid/200112250000_fake_migration.up.sql +++ /dev/null @@ -1 +0,0 @@ -/* Initialize the database version */ \ No newline at end of file diff --git a/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.down.sql b/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.down.sql deleted file mode 100644 index b9e984a8c..000000000 --- a/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.down.sql +++ /dev/null @@ -1 +0,0 @@ -/* Initialize the database version */ \ No newline at end of file diff --git a/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.up.sql b/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.up.sql deleted file mode 100644 index b9e984a8c..000000000 --- a/pkg/sqlite/testdata/test_db_migrations/valid/200412250000_fake_migration.up.sql +++ /dev/null @@ -1 +0,0 @@ -/* Initialize the database version */ \ No newline at end of file diff --git a/test/e2e/opm_test.go b/test/e2e/opm_test.go index e2cc8af90..a9428c010 100644 --- a/test/e2e/opm_test.go +++ b/test/e2e/opm_test.go @@ -1,13 +1,10 @@ package e2e_test import ( - "context" - "database/sql" "fmt" "os" "os/exec" "path/filepath" - "reflect" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -21,10 +18,6 @@ import ( "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" "github.com/operator-framework/operator-registry/pkg/image/execregistry" "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-registry/pkg/lib/indexer" - lregistry "github.com/operator-framework/operator-registry/pkg/lib/registry" - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-registry/pkg/sqlite" ) var ( @@ -32,45 +25,15 @@ var ( channels = "preview" defaultChannel = "preview" - bundlePath1 = "manifests/prometheus/0.14.0" - bundlePath2 = "manifests/prometheus/0.15.0" bundlePath3 = "manifests/prometheus/0.22.2" - bundleTag1 = rand.String(6) - bundleTag2 = rand.String(6) bundleTag3 = rand.String(6) - indexTag1 = rand.String(6) - indexTag2 = rand.String(6) - indexTag3 = rand.String(6) bundleImage string - indexImage string - indexImage1 string - indexImage2 string - indexImage3 string fbcIndexImageTag = dockerHost + "/olmtest/e2e-fbc" - fbcPackageName = "webhook-operator" - - // publishedIndex is an index used to check for regressions in opm's behavior. - publishedIndex = os.Getenv("PUBLISHED_INDEX") ) -type bundleLocation struct { - image, path string -} - -type bundleLocations []bundleLocation - -func (bl bundleLocations) images() []string { - images := make([]string, len(bl)) - for i, b := range bl { - images[i] = b.image - } - - return images -} - func inTemporaryBuildContext(f func() error, fromDir, toDir string) (rerr error) { td, err := os.MkdirTemp(".", "opm-") if err != nil { @@ -145,65 +108,6 @@ func buildFBCImage(imageTag, imageBuilder string, dockerFile string) (*exec.Cmd, return exec.Command(imageBuilder, args...), nil } -func buildIndexWith(containerTool, fromIndexImage, toIndexImage string, bundleImages []string, mode registry.Mode, overwriteLatest bool) error { - logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) - indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.AddToIndexRequest{ - Generate: false, - FromIndex: fromIndexImage, - BinarySourceImage: "", - OutDockerfile: "", - Tag: toIndexImage, - Mode: mode, - Bundles: bundleImages, - Permissive: false, - Overwrite: overwriteLatest, - SkipTLSVerify: *skipTLSForRegistry, - PlainHTTP: *useHTTPforRegistry, - } - - return indexAdder.AddToIndex(request) -} - -func buildFromIndexWith(containerTool string) error { - bundles := []string{ - bundleImage + ":" + bundleTag3, - } - logger := logrus.WithFields(logrus.Fields{"bundles": bundles}) - indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.AddToIndexRequest{ - Generate: false, - FromIndex: indexImage1, - BinarySourceImage: "", - OutDockerfile: "", - Tag: indexImage2, - Bundles: bundles, - Permissive: false, - } - - return indexAdder.AddToIndex(request) -} - -// TODO(djzager): make this more complete than what should be a simple no-op -func pruneIndexWith(containerTool string, fromIndex string, tag string, packageValue string) error { - logger := logrus.WithFields(logrus.Fields{"packages": packageValue}) - indexAdder := indexer.NewIndexPruner(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.PruneFromIndexRequest{ - Generate: false, - FromIndex: fromIndex, - BinarySourceImage: "", - OutDockerfile: "", - Tag: tag, - Packages: []string{packageValue}, - Permissive: false, - } - - return indexAdder.PruneFromIndex(request) -} - func pushWith(containerTool, image string) error { dockerpush := exec.Command(containerTool, "push", image) dockerpush.Stderr = GinkgoWriter @@ -211,71 +115,8 @@ func pushWith(containerTool, image string) error { return dockerpush.Run() } -func exportPackageWith(containerTool string) error { - packages := []string{packageName} - logger := logrus.WithFields(logrus.Fields{"package": packages}) - indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.ExportFromIndexRequest{ - Index: indexImage2, - Packages: packages, - DownloadPath: "downloaded", - ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool), - SkipTLSVerify: *skipTLSForRegistry, - PlainHTTP: *useHTTPforRegistry, - } - - return indexExporter.ExportFromIndex(request) -} - -func exportIndexImageWith(containerTool string) error { - logger := logrus.NewEntry(logrus.New()) - indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) - - request := indexer.ExportFromIndexRequest{ - Index: indexImage2, - Packages: []string{}, - DownloadPath: "downloaded", - ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool), - SkipTLSVerify: *skipTLSForRegistry, - PlainHTTP: *useHTTPforRegistry, - } - - return indexExporter.ExportFromIndex(request) -} - -func initialize() error { - tmpDB, err := os.CreateTemp("./", "index_tmp.db") - if err != nil { - return err - } - defer os.Remove(tmpDB.Name()) - - db, err := sqlite.Open(tmpDB.Name()) - if err != nil { - return err - } - defer db.Close() - - dbLoader, err := sqlite.NewSQLLiteLoader(db) - if err != nil { - return err - } - if err := dbLoader.Migrate(context.TODO()); err != nil { - return err - } - - loader := sqlite.NewSQLLoaderForDirectory(dbLoader, "downloaded") - return loader.Populate() -} - var _ = BeforeEach(func() { bundleImage = imageRegistry + "/e2e-bundle" - indexImage = imageRegistry + "/e2e-index" - indexImage1 = imageRegistry + "/e2e-index:" + indexTag1 - indexImage2 = imageRegistry + "/e2e-index:" + indexTag2 - indexImage3 = imageRegistry + "/e2e-index:" + indexTag3 - }) var _ = Describe("opm", func() { @@ -319,246 +160,16 @@ var _ = Describe("opm", func() { Expect(os.RemoveAll(unpackDir)).To(Succeed()) }) - It("builds and manipulates bundle and index images", func() { - By("building bundles") - bundles := bundleLocations{ - {bundleImage + ":" + bundleTag1, bundlePath1}, - {bundleImage + ":" + bundleTag2, bundlePath2}, - {bundleImage + ":" + bundleTag3, bundlePath3}, - } - var err error - for _, b := range bundles { - err = inTemporaryBuildContext(func() error { - return bundle.BuildFunc(b.path, "", b.image, containerTool, packageName, channels, defaultChannel, false, "scratch") - }, "../../manifests", "manifests") - Expect(err).NotTo(HaveOccurred()) - } - - By("pushing bundles") - for _, b := range bundles { - Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred()) - } - - By("building an index") - err = buildIndexWith(containerTool, "", indexImage1, bundles[:2].images(), registry.ReplacesMode, false) - Expect(err).NotTo(HaveOccurred()) - - By("pushing an index") - err = pushWith(containerTool, indexImage1) - Expect(err).NotTo(HaveOccurred()) - - By("building from an index") - err = buildFromIndexWith(containerTool) - Expect(err).NotTo(HaveOccurred()) - - By("pushing an index") - err = pushWith(containerTool, indexImage2) - Expect(err).NotTo(HaveOccurred()) - + It("builds and pushes a file-based catalog image", func() { By("building a fbc index") - err = buildFBCWith(containerTool, fbcIndexImageTag) + err := buildFBCWith(containerTool, fbcIndexImageTag) Expect(err).NotTo(HaveOccurred()) By("pushing a fbc index") err = pushWith(containerTool, fbcIndexImageTag) Expect(err).NotTo(HaveOccurred()) - - By("pruning an index") - err = pruneIndexWith(containerTool, indexImage2, indexImage3, packageName) - Expect(err).NotTo(HaveOccurred()) - - By("pruning a fbc index") - err = pruneIndexWith(containerTool, fbcIndexImageTag, indexImage3, fbcPackageName) - Expect(err).To(MatchError(indexer.ErrFileBasedCatalogPrune)) - - By("pushing an index") - err = pushWith(containerTool, indexImage3) - Expect(err).NotTo(HaveOccurred()) - - By("exporting a package from an index to disk") - err = exportPackageWith(containerTool) - Expect(err).NotTo(HaveOccurred()) - - By("loading manifests from a directory") - err = initialize() - Expect(err).NotTo(HaveOccurred()) - - // clean and try again with containerd - err = os.RemoveAll("downloaded") - Expect(err).NotTo(HaveOccurred()) - - By("exporting a package from an index to disk with containerd") - err = exportPackageWith(containertools.NoneTool.String()) - Expect(err).NotTo(HaveOccurred()) - - By("loading manifests from a containerd-extracted directory") - err = initialize() - Expect(err).NotTo(HaveOccurred()) - - // clean containerd-extracted directory - err = os.RemoveAll("downloaded") - Expect(err).NotTo(HaveOccurred()) - - By("exporting an entire index to disk") - err = exportIndexImageWith(containerTool) - Expect(err).NotTo(HaveOccurred()) - - By("loading manifests from a directory") - err = initialize() - Expect(err).NotTo(HaveOccurred()) }) - It("build bundles and index via inference", func() { - - bundles := bundleLocations{ - {bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.1"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.2"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.0"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1"}, - } - - By("building bundles") - for _, b := range bundles { - td, err := os.MkdirTemp(".", "opm-") - Expect(err).NotTo(HaveOccurred()) - defer os.RemoveAll(td) - - err = bundle.BuildFunc(b.path, td, b.image, containerTool, "", "", "", true, "scratch") - Expect(err).NotTo(HaveOccurred()) - } - - By("pushing bundles") - for _, b := range bundles { - Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred()) - } - - By("building an index") - indexImage := indexImage + ":" + rand.String(6) - err := buildIndexWith(containerTool, "", indexImage, bundles.images(), registry.ReplacesMode, false) - Expect(err).NotTo(HaveOccurred()) - }) - It("build index without bundles", func() { - indexImage := indexImage + ":" + rand.String(6) - By("building an index") - err := buildIndexWith(containerTool, "", indexImage, []string{}, registry.ReplacesMode, true) - Expect(err).NotTo(HaveOccurred()) - }) - - PIt("can overwrite existing bundles in an index", func() { - // TODO fix regression overwriting existing bundles in an index - bundles := bundleLocations{ - {bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.1"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.2"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.0"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1"}, - {bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1-overwrite"}, - } - - for _, b := range bundles { - td, err := os.MkdirTemp(".", "opm-") - Expect(err).NotTo(HaveOccurred()) - defer os.RemoveAll(td) - - err = bundle.BuildFunc(b.path, td, b.image, containerTool, "", "", "", true, "scratch") - Expect(err).NotTo(HaveOccurred()) - } - - By("pushing bundles") - for _, b := range bundles { - Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred()) - } - - indexImage := indexImage + ":" + rand.String(6) - By("adding net-new bundles to an index") - err := buildIndexWith(containerTool, "", indexImage, bundles[:4].images(), registry.ReplacesMode, true) // 0.0.1, 0.0.2, 1.0.0, 1.0.1 - Expect(err).NotTo(HaveOccurred()) - Expect(pushWith(containerTool, indexImage)).NotTo(HaveOccurred()) - - By("failing to overwrite a non-latest bundle") - nextIndex := indexImage + "-next" - err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[1:2].images(), registry.ReplacesMode, true) // 0.0.2 - Expect(err).To(HaveOccurred()) - - By("failing to overwrite in a non-replace mode") - err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[4:].images(), registry.SemVerMode, true) // 1.0.1-overwrite - Expect(err).To(HaveOccurred()) - - By("overwriting the latest bundle in an index") - err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[4:].images(), registry.ReplacesMode, true) // 1.0.1-overwrite - Expect(err).NotTo(HaveOccurred()) - }) - - It("doesn't change published content on overwrite", func() { - if publishedIndex == "" { - Skip("Set the PUBLISHED_INDEX environment variable to enable this test") - } - - logger := logrus.NewEntry(logrus.StandardLogger()) - logger.Logger.SetLevel(logrus.WarnLevel) - tool := containertools.NewContainerTool(containerTool, containertools.NoneTool) - imageIndexer := indexer.ImageIndexer{ - PullTool: tool, - Logger: logger, - } - dbFile, err := imageIndexer.ExtractDatabase(".", publishedIndex, "", *skipTLSForRegistry, *useHTTPforRegistry) - Expect(err).NotTo(HaveOccurred(), "error extracting registry db") - - db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", dbFile)) - Expect(err).NotTo(HaveOccurred(), "Error reading db file") - - querier := sqlite.NewSQLLiteQuerierFromDb(db) - - graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db) - Expect(err).NotTo(HaveOccurred(), "Error reading db file") - - packages, err := querier.ListPackages(context.TODO()) - Expect(err).NotTo(HaveOccurred(), "Error listing packages") - - var errs []error - - adder := lregistry.NewRegistryAdder(logger) - for _, pkg := range packages { - existing, err := graphLoader.Generate(pkg) - Expect(err).NotTo(HaveOccurred(), "Error generating graph for package %s", pkg, existing) - - for name, ch := range existing.Channels { - replacement, err := querier.GetBundleThatReplaces(context.TODO(), ch.Head.CsvName, pkg, name) - if err != nil && err.Error() != fmt.Errorf("no entry found for %s %s", pkg, name).Error() { - errs = append(errs, err) - continue - } - - if replacement != nil { - continue - } - - request := lregistry.AddToRegistryRequest{ - Permissive: false, - SkipTLSVerify: *skipTLSForRegistry, - PlainHTTP: *useHTTPforRegistry, - InputDatabase: dbFile, - Bundles: []string{ch.Head.BundlePath}, - Mode: registry.ReplacesMode, - ContainerTool: tool, - Overwrite: true, - } - - err = adder.AddToRegistry(request) - if err != nil { - errs = append(errs, fmt.Errorf("Error overwriting bundles for package %s: %s", pkg, err)) - } - } - - overwritten, err := graphLoader.Generate(pkg) - Expect(err).NotTo(HaveOccurred(), "Error generating graph for package %s", pkg) - - if !reflect.DeepEqual(existing, overwritten) { - errs = append(errs, fmt.Errorf("the update graph has changed during overwrite-latest for package: %s\nWas\n%s\nIs now\n%s", pkg, existing, overwritten)) - } - } - - Expect(errs).To(BeEmpty(), fmt.Sprintf("%s", errs)) - }) } Context("using docker", func() { diff --git a/upstream-builder.Dockerfile b/upstream-builder.Dockerfile index f276e7ed1..24c9428dd 100644 --- a/upstream-builder.Dockerfile +++ b/upstream-builder.Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.25-alpine AS builder -RUN apk update && apk add sqlite build-base git mercurial bash linux-headers +RUN apk update && apk add build-base git mercurial bash linux-headers WORKDIR /build COPY . . @@ -16,8 +16,5 @@ COPY ["nsswitch.conf", "/etc/nsswitch.conf"] COPY --from=builder [ \ "/bin/grpc_health_probe", \ "/build/bin/opm", \ - "/build/bin/initializer", \ - "/build/bin/configmap-server", \ - "/build/bin/registry-server", \ "/bin/" \ ]