Skip to content

Commit

Permalink
poc: direct bundle install API + implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
  • Loading branch information
joelanford committed Oct 31, 2024
1 parent ba71ce0 commit c86b589
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 19 deletions.
33 changes: 28 additions & 5 deletions api/v1alpha1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,44 @@ type ClusterExtensionSpec struct {
Install ClusterExtensionInstallConfig `json:"install"`
}

const SourceTypeCatalog = "Catalog"
const (
SourceTypeCatalog = "Catalog"
SourceTypeBundle = "Bundle"
)

// SourceConfig is a discriminated union which selects the installation source.
// +union
// +kubebuilder:validation:XValidation:rule="self.sourceType == 'Catalog' && has(self.catalog)",message="sourceType Catalog requires catalog field"

// +kubebuilder:validation:XValidation:rule="has(self.sourceType) && self.sourceType == 'Bundle' ?has(self.bundle) : !has(self.bundle)",message="bundle is required when sourceType is Bundle, and forbidden otherwise"
// +kubebuilder:validation:XValidation:rule="has(self.sourceType) && self.sourceType == 'Catalog' ?has(self.catalog) : !has(self.catalog)",message="catalog is required when sourceType is Catalog, and forbidden otherwise"
type SourceConfig struct {
// sourceType is a required reference to the type of install source.
//
// Allowed values are ["Catalog"]
// Allowed values are ["Catalog", "Bundle"]
//
// When this field is set to "Catalog", information for determining the appropriate
// bundle of content to install will be fetched from ClusterCatalog resources existing
// on the cluster. When using the Catalog sourceType, the catalog field must also be set.
//
// +unionDiscriminator
// +kubebuilder:validation:Enum:="Catalog"
// When this field is set to "Bundle", the bundle of content to install is specified
// directly. In this case, no interaction with ClusterCatalog resources is necessary.
// When using the Bundle sourceType, the bundle field must also be set.
//
// +unionDiscriminatorq
// +kubebuilder:validation:Enum:=Bundle;Catalog
SourceType string `json:"sourceType"`

// catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog",
// and must be the only field defined for this sourceType.
//
// +optional.
Catalog *CatalogSource `json:"catalog,omitempty"`

// bundle is used to configure how information is sourced from a bundle. This field must be defined when sourceType is set to "Bundle",
// and must be the only field defined for this sourceType.
//
// +optional.
Bundle *BundleSource `json:"bundle,omitempty"`
}

// ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config.
Expand Down Expand Up @@ -443,6 +458,14 @@ type CatalogSource struct {
UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"`
}

type BundleSource struct {
// ref is an OCI reference to an extension bundle image. Images referenced
// must conform to the registry+v1 bundle format.

//+kubebuilder:validation:Required
Ref string `json:"ref"`
}

// ServiceAccountReference references a serviceAccount.
type ServiceAccountReference struct {
// name is a required, immutable reference to the name of the ServiceAccount
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,11 @@ func main() {
return httputil.BuildHTTPClient(certPoolWatcher)
})

resolver := &resolve.CatalogResolver{
bundleResolver := &resolve.BundleResolver{
Unpacker: unpacker,
BrittleUnpackerCacheDir: unpacker.BaseCachePath,
}
catalogResolver := &resolve.CatalogResolver{
WalkCatalogsFunc: resolve.CatalogWalker(
func(ctx context.Context, option ...client.ListOption) ([]catalogd.ClusterCatalog, error) {
var catalogs catalogd.ClusterCatalogList
Expand All @@ -273,6 +277,9 @@ func main() {
resolve.NoDependencyValidation,
},
}
resolver := resolve.MultiResolver{}
resolver.RegisterType(ocv1alpha1.SourceTypeBundle, bundleResolver)
resolver.RegisterType(ocv1alpha1.SourceTypeCatalog, catalogResolver)

aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@ spec:
catalog:
packageName: example-package
properties:
bundle:
description: |-
bundle is used to configure how information is sourced from a bundle. This field must be defined when sourceType is set to "Bundle",
and must be the only field defined for this sourceType.
properties:
ref:
type: string
required:
- ref
type: object
catalog:
description: |-
catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog",
Expand Down Expand Up @@ -568,20 +578,31 @@ spec:
description: |-
sourceType is a required reference to the type of install source.
Allowed values are ["Catalog"]
Allowed values are ["Catalog", "Bundle"]
When this field is set to "Catalog", information for determining the appropriate
bundle of content to install will be fetched from ClusterCatalog resources existing
on the cluster. When using the Catalog sourceType, the catalog field must also be set.
When this field is set to "Bundle", the bundle of content to install is specified
directly. In this case, no interaction with ClusterCatalog resources is necessary.
When using the Bundle sourceType, the bundle field must also be set.
enum:
- Bundle
- Catalog
type: string
required:
- sourceType
type: object
x-kubernetes-validations:
- message: sourceType Catalog requires catalog field
rule: self.sourceType == 'Catalog' && has(self.catalog)
- message: bundle is required when sourceType is Bundle, and forbidden
otherwise
rule: 'has(self.sourceType) && self.sourceType == ''Bundle'' ?has(self.bundle)
: !has(self.bundle)'
- message: catalog is required when sourceType is Catalog, and forbidden
otherwise
rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ?has(self.catalog)
: !has(self.catalog)'
required:
- install
- source
Expand Down
7 changes: 3 additions & 4 deletions config/samples/cloudnative-pg-clusterextension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ metadata:
name: cloudnative-pg
spec:
source:
sourceType: Catalog
catalog:
packageName: cloudnative-pg
version: "1.24.1"
sourceType: Bundle
bundle:
ref: quay.io/operatorhubio/cloudnative-pg@sha256:e960f799f3d2b2dd5ecc74bc576476fe9c70de6486ba5ffc7d6ef333bba186bc
install:
namespace: cloudnative-pg
serviceAccount:
Expand Down
9 changes: 5 additions & 4 deletions config/samples/olm_v1alpha1_clusterextension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,12 @@ metadata:
name: argocd
spec:
source:
sourceType: Catalog
catalog:
packageName: argocd-operator
version: 0.6.0
sourceType: Bundle
bundle:
ref: quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3
install:
namespace: argocd
serviceAccount:
name: argocd-installer
values:
watchNamespace: argocd
21 changes: 19 additions & 2 deletions docs/api-reference/operator-controller-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ _Appears in:_
| `version` _string_ | version is a required field and is a reference<br />to the version that this bundle represents | | |


#### BundleSource







_Appears in:_
- [SourceConfig](#sourceconfig)

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `ref` _string_ | | | Required: \{\} <br /> |


#### CRDUpgradeSafetyPolicy

_Underlying type:_ _string_
Expand Down Expand Up @@ -248,7 +264,7 @@ _Appears in:_



SourceConfig is a discriminated union which selects the installation source.




Expand All @@ -257,8 +273,9 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `sourceType` _string_ | sourceType is a required reference to the type of install source.<br /><br />Allowed values are ["Catalog"]<br /><br />When this field is set to "Catalog", information for determining the appropriate<br />bundle of content to install will be fetched from ClusterCatalog resources existing<br />on the cluster. When using the Catalog sourceType, the catalog field must also be set. | | Enum: [Catalog] <br /> |
| `sourceType` _string_ | sourceType is a required reference to the type of install source.<br /><br />Allowed values are ["Catalog", "Bundle"]<br /><br />When this field is set to "Catalog", information for determining the appropriate<br />bundle of content to install will be fetched from ClusterCatalog resources existing<br />on the cluster. When using the Catalog sourceType, the catalog field must also be set.<br /><br />When this field is set to "Bundle", the bundle of content to install is specified<br />directly. In this case, no interaction with ClusterCatalog resources is necessary.<br />When using the Bundle sourceType, the bundle field must also be set. | | Enum: [Bundle Catalog] <br /> |
| `catalog` _[CatalogSource](#catalogsource)_ | catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog",<br />and must be the only field defined for this sourceType. | | |
| `bundle` _[BundleSource](#bundlesource)_ | bundle is used to configure how information is sourced from a bundle. This field must be defined when sourceType is set to "Bundle",<br />and must be the only field defined for this sourceType. | | |


#### UpgradeConstraintPolicy
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ require (
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-migrate/migrate/v4 v4.18.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,8 @@ go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
Expand Down
72 changes: 72 additions & 0 deletions internal/resolve/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package resolve

import (
"context"
"errors"
"fmt"
"path/filepath"

bsemver "github.com/blang/semver/v4"
"github.com/containers/image/v5/docker/reference"

"github.com/operator-framework/operator-registry/alpha/action"
"github.com/operator-framework/operator-registry/alpha/declcfg"

ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
"github.com/operator-framework/operator-controller/internal/bundleutil"
"github.com/operator-framework/operator-controller/internal/rukpak/source"
)

type BundleResolver struct {
Unpacker source.Unpacker
BrittleUnpackerCacheDir string
}

func (r *BundleResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
res, err := r.Unpacker.Unpack(ctx, &source.BundleSource{
Name: ext.Name,
Type: source.SourceTypeImage,
Image: &source.ImageSource{
Ref: ext.Spec.Source.Bundle.Ref,
},
})
if err != nil {
return nil, nil, nil, err
}
if res.State != source.StateUnpacked {
return nil, nil, nil, fmt.Errorf("bundle not unpacked: %v", res.Message)
}

ref, err := reference.ParseNamed(res.ResolvedSource.Image.Ref)
if err != nil {
return nil, nil, nil, err
}
canonicalRef, ok := ref.(reference.Canonical)
if !ok {
return nil, nil, nil, errors.New("expected canonical reference")
}
bundlePath := filepath.Join(r.BrittleUnpackerCacheDir, ext.Name, canonicalRef.Digest().String())

// TODO: This is a temporary workaround to get the bundle from the filesystem
// until the operator-registry library is updated to support reading from a
// filesystem. This will be removed once the library is updated.

render := action.Render{
Refs: []string{bundlePath},
AllowedRefMask: action.RefBundleDir,
}
fbc, err := render.Run(ctx)
if err != nil {
return nil, nil, nil, err
}
if len(fbc.Bundles) != 1 {
return nil, nil, nil, errors.New("expected exactly one bundle")
}
bundle := fbc.Bundles[0]
bundle.Image = canonicalRef.String()
v, err := bundleutil.GetVersion(bundle)
if err != nil {
return nil, nil, nil, err
}
return &bundle, v, nil, nil
}
16 changes: 16 additions & 0 deletions internal/resolve/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package resolve

import (
"context"
"fmt"

bsemver "github.com/blang/semver/v4"

Expand All @@ -19,3 +20,18 @@ type Func func(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedB
func (f Func) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
return f(ctx, ext, installedBundle)
}

type MultiResolver map[string]Resolver

func (m MultiResolver) RegisterType(sourceType string, r Resolver) {
m[sourceType] = r
}

func (m MultiResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
t := ext.Spec.Source.SourceType
r, ok := m[t]
if !ok {
return nil, nil, nil, fmt.Errorf("no resolver for source type %q", t)
}
return r.Resolve(ctx, ext, installedBundle)
}

0 comments on commit c86b589

Please sign in to comment.