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 Nov 22, 2024
1 parent ae1ec78 commit 3d16104
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 16 deletions.
76 changes: 72 additions & 4 deletions api/v1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,41 @@ type ClusterExtensionSpec struct {
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`
}

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

// SourceConfig is a discriminated union which selects the installation source.
//
// +union
// +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 "Bundle" or "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.
//
// 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"
// +kubebuilder:validation:Enum:=Bundle;Catalog
// +kubebuilder:validation:Required
SourceType string `json:"sourceType"`

// bundle is used to configure how information is sourced from a bundle.
// This field is required when sourceType is "Bundle", and forbidden otherwise.
//
// +optional.
Bundle *BundleSource `json:"bundle,omitempty"`

// catalog is used to configure how information is sourced from a catalog.
// This field is required when sourceType is "Catalog", and forbidden otherwise.
//
Expand Down Expand Up @@ -444,7 +458,61 @@ type CatalogSource struct {
UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"`
}

// ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension.
// BundleSource defines the configuration used to retrieve a bundle directly from
// its OCI-based image reference.
type BundleSource struct {
// ref allows users to define the reference to a container image containing bundle contents.
// ref is required.
// ref can not be more than 1000 characters.
//
// A reference can be broken down into 3 parts - the domain, name, and identifier.
//
// The domain is typically the registry where an image is located.
// It must be alphanumeric characters (lowercase and uppercase) separated by the "." character.
// Hyphenation is allowed, but the domain must start and end with alphanumeric characters.
// Specifying a port to use is also allowed by adding the ":" character followed by numeric values.
// The port must be the last value in the domain.
// Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080".
//
// The name is typically the repository in the registry where an image is located.
// It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters.
// Multiple names can be concatenated with the "/" character.
// The domain and name are combined using the "/" character.
// Some examples of valid name values are "operatorhubio/bundle", "bundle", "my-bundle.prod".
// An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/bundle".
//
// The identifier is typically the tag or digest for an image reference and is present at the end of the reference.
// It starts with a separator character used to distinguish the end of the name and beginning of the identifier.
// For a digest-based reference, the "@" character is the separator.
// For a tag-based reference, the ":" character is the separator.
// An identifier is required in the reference.
//
// Digest-based references must contain an algorithm reference immediately after the "@" separator.
// The algorithm reference must be followed by the ":" character and an encoded string.
// The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters.
// Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58".
// The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters.
//
// Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters.
// The tag must not be longer than 127 characters.
//
// An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05"
// An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest"
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength:=1000
// +kubebuilder:validation:XValidation:rule="self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')",message="must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character."
// +kubebuilder:validation:XValidation:rule="self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\"",message="a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters."
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" || self.find(':.*$') != \"\"",message="must end with a digest or a tag"
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').substring(1).size() <= 127 : true) : true",message="tag is invalid. the tag must not be more than 127 characters"
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').matches(':[\\\\w][\\\\w.-]*$') : true) : true",message="tag is invalid. valid tags must begin with a word character (alphanumeric + \"_\") followed by word characters or \".\", and \"-\" characters"
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find('(@.*:)').matches('(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])') : true",message="digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters."
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').substring(1).size() >= 32 : true",message="digest is not valid. the encoded string must be at least 32 characters"
// +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').matches(':[0-9A-Fa-f]*$') : true",message="digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)"
Ref string `json:"ref"`
}

// ServiceAccountReference identifies the serviceAccount used to install a ClusterExtension.
type ServiceAccountReference struct {
// name is a required, immutable reference to the name of the ServiceAccount
// to be used for installation and management of the content for the package
Expand Down
20 changes: 20 additions & 0 deletions api/v1/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(ocv1.SourceTypeBundle, bundleResolver)
resolver.RegisterType(ocv1.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 @@ -190,6 +190,93 @@ spec:
catalog:
packageName: example-package
properties:
bundle:
description: |-
bundle is used to configure how information is sourced from a bundle.
This field is required when sourceType is "Bundle", and forbidden otherwise.
properties:
ref:
description: |-
ref allows users to define the reference to a container image containing bundle contents.
ref is required.
ref can not be more than 1000 characters.
A reference can be broken down into 3 parts - the domain, name, and identifier.
The domain is typically the registry where an image is located.
It must be alphanumeric characters (lowercase and uppercase) separated by the "." character.
Hyphenation is allowed, but the domain must start and end with alphanumeric characters.
Specifying a port to use is also allowed by adding the ":" character followed by numeric values.
The port must be the last value in the domain.
Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080".
The name is typically the repository in the registry where an image is located.
It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters.
Multiple names can be concatenated with the "/" character.
The domain and name are combined using the "/" character.
Some examples of valid name values are "operatorhubio/bundle", "bundle", "my-bundle.prod".
An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/bundle".
The identifier is typically the tag or digest for an image reference and is present at the end of the reference.
It starts with a separator character used to distinguish the end of the name and beginning of the identifier.
For a digest-based reference, the "@" character is the separator.
For a tag-based reference, the ":" character is the separator.
An identifier is required in the reference.
Digest-based references must contain an algorithm reference immediately after the "@" separator.
The algorithm reference must be followed by the ":" character and an encoded string.
The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters.
Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58".
The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters.
Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters.
The tag must not be longer than 127 characters.
An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05"
An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest"
maxLength: 1000
type: string
x-kubernetes-validations:
- message: must start with a valid domain. valid domains must
be alphanumeric characters (lowercase and uppercase) separated
by the "." character.
rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b')
- message: a valid name is required. valid names must contain
lowercase alphanumeric characters separated only by the
".", "_", "__", "-" characters.
rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)')
!= ""
- message: must end with a digest or a tag
rule: self.find('(@.*:)') != "" || self.find(':.*$') !=
""
- message: tag is invalid. the tag must not be more than 127
characters
rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'')
!= "" ? self.find('':.*$'').substring(1).size() <= 127
: true) : true'
- message: tag is invalid. valid tags must begin with a word
character (alphanumeric + "_") followed by word characters
or ".", and "-" characters
rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'')
!= "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'')
: true) : true'
- message: digest algorithm is not valid. valid algorithms
must start with an uppercase or lowercase alpha character
followed by alphanumeric characters and may contain the
"-", "_", "+", and "." characters.
rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'')
: true'
- message: digest is not valid. the encoded string must be
at least 32 characters
rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size()
>= 32 : true'
- message: digest is not valid. the encoded string must only
contain hex characters (A-F, a-f, 0-9)
rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'')
: true'
required:
- ref
type: object
catalog:
description: |-
catalog is used to configure how information is sourced from a catalog.
Expand Down Expand Up @@ -439,19 +526,28 @@ spec:
description: |-
sourceType is a required reference to the type of install source.
Allowed values are "Catalog"
Allowed values are "Bundle" or "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.
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.
enum:
- Bundle
- Catalog
type: string
required:
- sourceType
type: object
x-kubernetes-validations:
- 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'' ?
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
12 changes: 8 additions & 4 deletions config/samples/olm_v1_clusterextension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ spec:
serviceAccount:
name: argocd-installer
source:
sourceType: Catalog
catalog:
packageName: argocd-operator
version: 0.6.0
sourceType: Bundle
bundle:
ref: quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3
template:
valuesSources:
- type: Inline
inline:
watchNamespace: argocd
Loading

0 comments on commit 3d16104

Please sign in to comment.