Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
joelanford committed Aug 11, 2023
1 parent dea70eb commit f12acc7
Show file tree
Hide file tree
Showing 42 changed files with 658 additions and 3,351 deletions.
29 changes: 28 additions & 1 deletion api/v1alpha1/operator_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ type OperatorSpec struct {
PackageName string `json:"packageName"`

//+kubebuilder:validation:MaxLength:=64
//+kubebuilder:validation:Pattern=^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$
// TODO: add this pattern back in with masterminds range syntax support
// => +kubebuilder:validation:Pattern=^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$
//+kubebuilder:Optional

// Version is an optional semver constraint on the package version. If not specified, the latest version available of the package will be installed.
// If specified, the specific version of the package will be installed so long as it is available in any of the content sources available.
// Examples: 1.2.3, 1.0.0-alpha, 1.0.0-rc.1
Expand All @@ -40,8 +42,27 @@ type OperatorSpec struct {

//+kubebuilder:validation:MaxLength:=48
//+kubebuilder:validation:Pattern:=^[a-z0-9]+([\.-][a-z0-9]+)*$

// Channel constraint definition
Channel string `json:"channel,omitempty"`

//+kubebuilder:validation:enum:=Enforce;Ignore
//+kubebuilder:default:=Enforce
//+kubebuilder:validation:Optional

// UpgradeEdgeConstraintPolicy defines the policy for how to handle upgrades. If set to Enforce, the operator will only upgrade
// if the new version satisfies the edge constraint set by the extension author. If set to Ignore, the operator can be upgraded
// or downgraded, regardless of edge constraint.
UpgradeEdgeConstraintPolicy string `json:"upgradeEdgeConstraintPolicy,omitempty"`

//+kubebuilder:validation:enum:=Enforce;Ignore
//+kubebuilder:default:=Enforce
//+kubebuilder:validation:Optional

// ClusterConstraintPolicy defines the policy for how to handle cluster constraints defined by the bundle. If set to Enforce,
// resolved bundles must satisfy all cluster constraints. If set to Ignore, resolved bundles can be installed regardless of
// their cluster constraints.
ClusterConstraintPolicy string `json:"clusterConstraintPolicy,omitempty"`
}

const (
Expand All @@ -57,6 +78,12 @@ const (
ReasonResolutionFailed = "ResolutionFailed"
ReasonResolutionUnknown = "ResolutionUnknown"
ReasonSuccess = "Success"

UpgradeEdgeConstraintPolicyEnforce = "Enforce"
UpgradeEdgeConstraintPolicyIgnore = "Ignore"

ClusterConstraintPolicyEnforce = "Enforce"
ClusterConstraintPolicyIgnore = "Ignore"
)

func init() {
Expand Down
27 changes: 20 additions & 7 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,24 @@ import (
"flag"
"os"

catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/deppy/pkg/deppy/solver"
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
"github.com/spf13/pflag"
"go.uber.org/zap/zapcore"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/discovery"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/deppy/pkg/deppy/solver"
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"

operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
"github.com/operator-framework/operator-controller/internal/controllers"
"github.com/operator-framework/operator-controller/internal/resolution/entitysources"
"github.com/operator-framework/operator-controller/internal/resolution/v2/store"
"github.com/operator-framework/operator-controller/internal/resolution/v2/variablesources"
"github.com/operator-framework/operator-controller/pkg/features"
)

Expand Down Expand Up @@ -99,12 +100,24 @@ func main() {
os.Exit(1)
}

discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create discovery client")
os.Exit(1)
}

if err = (&controllers.OperatorReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Resolver: solver.NewDeppySolver(
entitysources.NewCatalogdEntitySource(mgr.GetClient()),
controllers.NewVariableSource(mgr.GetClient()),
nil,
variablesources.Operator{
Client: mgr.GetClient(),
Store: store.CatalogMetadataStore{
Client: mgr.GetClient(),
ServerVersionInterface: discoveryClient,
},
},
),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Operator")
Expand Down
17 changes: 16 additions & 1 deletion config/crd/bases/operators.operatorframework.io_operators.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,26 @@ spec:
maxLength: 48
pattern: ^[a-z0-9]+([\.-][a-z0-9]+)*$
type: string
clusterConstraintPolicy:
default: Enforce
description: ClusterConstraintPolicy defines the policy for how to
handle cluster constraints defined by the bundle. If set to Enforce,
resolved bundles must satisfy all cluster constraints. If set to
Ignore, resolved bundles can be installed regardless of their cluster
constraints.
type: string
packageName:
maxLength: 48
pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
type: string
upgradeEdgeConstraintPolicy:
default: Enforce
description: UpgradeEdgeConstraintPolicy defines the policy for how
to handle upgrades. If set to Enforce, the operator will only upgrade
if the new version satisfies the edge constraint set by the extension
author. If set to Ignore, the operator can be upgraded or downgraded,
regardless of edge constraint.
type: string
version:
description: "Version is an optional semver constraint on the package
version. If not specified, the latest version available of the package
Expand All @@ -51,7 +67,6 @@ spec:
sources available. Examples: 1.2.3, 1.0.0-alpha, 1.0.0-rc.1 \n For
more information on semver, please see https://semver.org/"
maxLength: 64
pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$
type: string
required:
- packageName
Expand Down
14 changes: 14 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- catalogd.operatorframework.io
resources:
- bundlemetadata
verbs:
- list
- watch
- apiGroups:
- catalogd.operatorframework.io
resources:
- catalogmetadata
verbs:
- list
- watch
- apiGroups:
- catalogd.operatorframework.io
resources:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/operator-framework/operator-controller
go 1.20

require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/blang/semver/v4 v4.0.0
github.com/go-logr/logr v1.2.4
github.com/onsi/ginkgo/v2 v2.11.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
Expand Down
71 changes: 46 additions & 25 deletions internal/controllers/operator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ package controllers

import (
"context"
"encoding/json"
"errors"
"fmt"

"github.com/go-logr/logr"
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/solver"
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
"k8s.io/apimachinery/pkg/api/equality"
Expand All @@ -41,8 +44,8 @@ import (

operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
"github.com/operator-framework/operator-controller/internal/controllers/validators"
"github.com/operator-framework/operator-controller/internal/resolution/entities"
olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables"
"github.com/operator-framework/operator-controller/internal/resolution/v2/store"
"github.com/operator-framework/operator-controller/internal/resolution/v2/variables"
)

// OperatorReconciler reconciles a Operator object
Expand All @@ -60,8 +63,11 @@ type OperatorReconciler struct {

//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=bundlemetadata,verbs=list;watch
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=packages,verbs=list;watch
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogmetadata,verbs=list;watch
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogs,verbs=list;watch

//+kubebuilder:rbac:groups="",resources=nodes,verbs=list;watch

func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx).WithName("operator-controller")
l.V(1).Info("starting")
Expand Down Expand Up @@ -136,45 +142,58 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha
return ctrl.Result{}, err
}

// lookup the bundle entity in the solution that corresponds to the
// Operator's desired package name.
bundleEntity, err := r.getBundleEntityFromSolution(solution, op.Spec.PackageName)
if err != nil {
unsat := deppy.NotSatisfiable{}
if errors.As(solution.Error(), &unsat); len(unsat) > 0 {
err := unsat
op.Status.InstalledBundleResource = ""
setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution failed", op.GetGeneration())
op.Status.ResolvedBundleResource = ""
setResolvedStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration())
return ctrl.Result{}, err
}

// Get the bundle image reference for the bundle
bundleImage, err := bundleEntity.BundlePath()
// lookup the bundle entity in the solution that corresponds to the
// Operator's desired package name.
bundleEntity, err := r.getBundleFromSolution(solution, op.Spec.PackageName)
if err != nil {
op.Status.InstalledBundleResource = ""
setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution failed", op.GetGeneration())

op.Status.ResolvedBundleResource = ""
setResolvedStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration())
return ctrl.Result{}, err
}

// Get the bundle image reference for the bundle
bundleImage := bundleEntity.Image

// Now we can set the Resolved Condition, and the resolvedBundleSource field to the bundleImage value.
op.Status.ResolvedBundleResource = bundleImage
setResolvedStatusConditionSuccess(&op.Status.Conditions, fmt.Sprintf("resolved to %q", bundleImage), op.GetGeneration())

mediaType, err := bundleEntity.MediaType()
if err != nil {
setInstalledStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration())
return ctrl.Result{}, err
mediaType := store.MediaTypeRegistryV1
for _, p := range bundleEntity.Properties {
if p.Type == store.PropertyBundleMediaType {
var v string
if err := json.Unmarshal(p.Value, &v); err == nil {
op.Status.InstalledBundleResource = ""
setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution failed", op.GetGeneration())
op.Status.ResolvedBundleResource = ""
err = fmt.Errorf("failed to parse bundle mediatype %q: %w", string(p.Value), err)
setResolvedStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration())
return ctrl.Result{}, err
}
mediaType = v
}
}

bundleProvisioner, err := mapBundleMediaTypeToBundleProvisioner(mediaType)
if err != nil {
setInstalledStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration())
return ctrl.Result{}, err
}
// Ensure a BundleDeployment exists with its bundle source from the bundle
// image we just looked up in the solution.
dep := r.generateExpectedBundleDeployment(*op, bundleImage, bundleProvisioner)
dep := r.generateExpectedBundleDeployment(*op, bundleEntity, bundleProvisioner)
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
// originally Reason: operatorsv1alpha1.ReasonInstallationFailed
op.Status.InstalledBundleResource = ""
Expand Down Expand Up @@ -244,23 +263,20 @@ func mapBDStatusToInstalledCondition(existingTypedBundleDeployment *rukpakv1alph
}
}

func (r *OperatorReconciler) getBundleEntityFromSolution(solution *solver.Solution, packageName string) (*entities.BundleEntity, error) {
func (r *OperatorReconciler) getBundleFromSolution(solution *solver.Solution, packageName string) (*store.Bundle, error) {
for _, variable := range solution.SelectedVariables() {
switch v := variable.(type) {
case *olmvariables.BundleVariable:
entityPkgName, err := v.BundleEntity().PackageName()
if err != nil {
return nil, err
}
case *variables.Bundle:
entityPkgName := v.Bundle.Package
if packageName == entityPkgName {
return v.BundleEntity(), nil
return v.Bundle, nil
}
}
}
return nil, fmt.Errorf("entity for package %q not found in solution", packageName)
}

func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string, bundleProvisioner string) *unstructured.Unstructured {
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundle *store.Bundle, bundleProvisioner string) *unstructured.Unstructured {
// We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver.
// If you use a typed object, any default values from that struct get serialized into the JSON patch, which could
// cause unrelated fields to be patched back to the default value even though that isn't the intention. Using an
Expand All @@ -272,6 +288,11 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha
"kind": rukpakv1alpha1.BundleDeploymentKind,
"metadata": map[string]interface{}{
"name": o.GetName(),
"annotations": map[string]string{
"operators.operatorframework.io/package": bundle.Package,
"operators.operatorframework.io/name": bundle.Name,
"operators.operatorframework.io/version": bundle.Version.String(),
},
},
"spec": map[string]interface{}{
// TODO: Don't assume plain provisioner
Expand All @@ -283,7 +304,7 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha
// TODO: Don't assume image type
"type": string(rukpakv1alpha1.SourceTypeImage),
"image": map[string]interface{}{
"ref": bundlePath,
"ref": bundle.Image,
},
},
},
Expand Down Expand Up @@ -358,13 +379,13 @@ func (r *OperatorReconciler) existingBundleDeploymentUnstructured(ctx context.Co
// rukpak bundle provisioner class name that is capable of unpacking the bundle type
func mapBundleMediaTypeToBundleProvisioner(mediaType string) (string, error) {
switch mediaType {
case entities.MediaTypePlain:
case store.MediaTypePlainV0:
return "core-rukpak-io-plain", nil
// To ensure compatibility with bundles created with OLMv0 where the
// olm.bundle.mediatype property doesn't exist, we assume that if the
// property is empty (i.e doesn't exist) that the bundle is one created
// with OLMv0 and therefore should use the registry provisioner
case entities.MediaTypeRegistry, "":
case store.MediaTypeRegistryV1, "":
return "core-rukpak-io-registry", nil
default:
return "", fmt.Errorf("unknown bundle mediatype: %s", mediaType)
Expand Down
2 changes: 1 addition & 1 deletion internal/controllers/validators/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func validateSemver(operator *operatorsv1alpha1.Operator) error {
// ValidateOperatorSpec validates the operator spec, e.g. ensuring that .spec.version, if provided, is a valid SemVer
func ValidateOperatorSpec(operator *operatorsv1alpha1.Operator) error {
validators := []operatorCRValidatorFunc{
validateSemver,
//∂validateSemver,
}

// TODO: currently we only have a single validator, but more will likely be added in the future
Expand Down
Loading

0 comments on commit f12acc7

Please sign in to comment.