Skip to content

Commit

Permalink
Translate legacy "bundle dependencies" to properties.
Browse files Browse the repository at this point in the history
Operator dependencies are expressed as properties, but there was
initially a notion of dependencies as distinct from properties. For
that reason, catalog-operator must support existing catalog images
that serve separate Bundle.dependencies and Bundle.properties fields
by translating the three legacy dependency types (olm.label,
olm.package, and olm.gvk) into their corresponding properties.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
  • Loading branch information
benluddy committed Jul 13, 2021
1 parent 568d145 commit c918a85
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 141 deletions.
20 changes: 13 additions & 7 deletions pkg/controller/registry/resolver/installabletypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,30 @@ func NewBundleInstallableFromOperator(o *Operator) (BundleInstallable, error) {
if src == nil {
return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Identifier())
}
id := bundleId(o.Identifier(), o.Channel(), src.Catalog)
var constraints []solver.Constraint
if src.Catalog.Virtual() && src.Subscription == nil {
// CSVs already associated with a Subscription
// may be replaced, but freestanding CSVs must
// appear in any solution.
constraints = append(constraints, PrettyConstraint(
solver.Mandatory(),
fmt.Sprintf("clusterserviceversion %s exists and is not referenced by a subscription", o.Identifier()),
))
}
for _, p := range o.bundle.GetProperties() {
if p.GetType() == operatorregistry.DeprecatedType {
constraints = append(constraints, PrettyConstraint(
solver.Prohibited(),
fmt.Sprintf("bundle %s is deprecated", bundleId(o.Identifier(), o.Channel(), src.Catalog)),
fmt.Sprintf("bundle %s is deprecated", id),
))
break
}
}
return NewBundleInstallable(o.Identifier(), o.Channel(), src.Catalog, constraints...), nil
}

func NewBundleInstallable(bundle, channel string, catalog registry.CatalogKey, constraints ...solver.Constraint) BundleInstallable {
return BundleInstallable{
identifier: bundleId(bundle, channel, catalog),
identifier: id,
constraints: constraints,
}
}, nil
}

type GenericInstallable struct {
Expand Down
173 changes: 121 additions & 52 deletions pkg/controller/registry/resolver/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
"github.com/operator-framework/operator-registry/pkg/api"
opregistry "github.com/operator-framework/operator-registry/pkg/registry"

"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
)

type APISet map[opregistry.APIKey]struct{}
Expand Down Expand Up @@ -197,6 +196,7 @@ type OperatorSourceInfo struct {
StartingCSV string
Catalog registry.CatalogKey
DefaultChannel bool
Subscription *v1alpha1.Subscription
}

func (i *OperatorSourceInfo) String() string {
Expand All @@ -216,7 +216,6 @@ type OperatorSurface interface {
SourceInfo() *OperatorSourceInfo
Bundle() *api.Bundle
Inline() bool
Dependencies() []*api.Dependency
Properties() []*api.Property
Skips() []string
}
Expand All @@ -229,7 +228,6 @@ type Operator struct {
version *semver.Version
bundle *api.Bundle
sourceInfo *OperatorSourceInfo
dependencies []*api.Dependency
properties []*api.Property
skips []string
}
Expand Down Expand Up @@ -261,21 +259,27 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg
// legacy support - if the api doesn't contain properties/dependencies, build them from required/provided apis
properties := bundle.Properties
if len(properties) == 0 {
properties, err = apisToProperties(provided)
properties, err = providedAPIsToProperties(provided)
if err != nil {
return nil, err
}
}
dependencies := bundle.Dependencies
if len(dependencies) == 0 {
dependencies, err = apisToDependencies(required)
if len(bundle.Dependencies) > 0 {
ps, err := legacyDependenciesToProperties(bundle.Dependencies)
if err != nil {
return nil, fmt.Errorf("failed to translate legacy dependencies to properties: %w", err)
}
properties = append(properties, ps...)
} else {
ps, err := requiredAPIsToProperties(required)
if err != nil {
return nil, err
}
properties = append(properties, ps...)
}

// legacy support - if the grpc api doesn't contain required/provided apis, fallback to csv parsing
if len(required) == 0 && len(provided) == 0 && len(properties) == 0 && len(dependencies) == 0 {
if len(required) == 0 && len(provided) == 0 && len(properties) == 0 {
// fallback to csv parsing
if bundle.CsvJson == "" {
if bundle.GetBundlePath() != "" {
Expand Down Expand Up @@ -307,7 +311,6 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg
requiredAPIs: required,
bundle: bundle,
sourceInfo: sourceInfo,
dependencies: dependencies,
properties: properties,
skips: bundle.Skips,
}, nil
Expand Down Expand Up @@ -338,23 +341,22 @@ func NewOperatorFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*Operator,
requiredAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
}

dependencies, err := apisToDependencies(requiredAPIs)
properties, err := providedAPIsToProperties(providedAPIs)
if err != nil {
return nil, err
}

properties, err := apisToProperties(providedAPIs)
dependencies, err := requiredAPIsToProperties(requiredAPIs)
if err != nil {
return nil, err
}
properties = append(properties, dependencies...)

return &Operator{
name: csv.GetName(),
version: &csv.Spec.Version.Version,
providedAPIs: providedAPIs,
requiredAPIs: requiredAPIs,
sourceInfo: &ExistingOperator,
dependencies: dependencies,
properties: properties,
}, nil
}
Expand Down Expand Up @@ -413,48 +415,48 @@ func (o *Operator) Inline() bool {
return o.bundle != nil && o.bundle.GetBundlePath() == ""
}

func (o *Operator) Dependencies() []*api.Dependency {
return o.dependencies
}

func (o *Operator) Properties() []*api.Property {
return o.properties
}

func (o *Operator) DependencyPredicates() (predicates []OperatorPredicate, err error) {
predicates = make([]OperatorPredicate, 0)
for _, d := range o.Dependencies() {
var p OperatorPredicate
if d == nil || d.Type == "" {
continue
}
p, err = PredicateForDependency(d)
for _, property := range o.Properties() {
predicate, err := PredicateForProperty(property)
if err != nil {
return
return nil, err
}
if predicate == nil {
continue
}
predicates = append(predicates, p)
predicates = append(predicates, predicate)
}
return
}

// TODO: this should go in its own dependency/predicate builder package
// TODO: can we make this more extensible, i.e. via cue
func PredicateForDependency(dependency *api.Dependency) (OperatorPredicate, error) {
p, ok := predicates[dependency.Type]
func PredicateForProperty(property *api.Property) (OperatorPredicate, error) {
if property == nil {
return nil, nil
}
p, ok := predicates[property.Type]
if !ok {
return nil, fmt.Errorf("no predicate for dependency type %s", dependency.Type)
return nil, nil
}
return p(dependency.Value)
return p(property.Value)
}

var predicates = map[string]func(string) (OperatorPredicate, error){
opregistry.GVKType: predicateForGVKDependency,
opregistry.PackageType: predicateForPackageDependency,
opregistry.LabelType: predicateForLabelDependency,
"olm.gvk.required": predicateForRequiredGVKProperty,
"olm.package.required": predicateForRequiredPackageProperty,
"olm.label.required": predicateForRequiredLabelProperty,
}

func predicateForGVKDependency(value string) (OperatorPredicate, error) {
var gvk opregistry.GVKDependency
func predicateForRequiredGVKProperty(value string) (OperatorPredicate, error) {
var gvk struct {
Group string `json:"group"`
Version string `json:"version"`
Kind string `json:"kind"`
}
if err := json.Unmarshal([]byte(value), &gvk); err != nil {
return nil, err
}
Expand All @@ -465,44 +467,51 @@ func predicateForGVKDependency(value string) (OperatorPredicate, error) {
}), nil
}

func predicateForPackageDependency(value string) (OperatorPredicate, error) {
var pkg opregistry.PackageDependency
func predicateForRequiredPackageProperty(value string) (OperatorPredicate, error) {
var pkg struct {
PackageName string `json:"packageName"`
VersionRange string `json:"versionRange"`
}
if err := json.Unmarshal([]byte(value), &pkg); err != nil {
return nil, err
}
ver, err := semver.ParseRange(pkg.Version)
ver, err := semver.ParseRange(pkg.VersionRange)
if err != nil {
return nil, err
}

return And(WithPackage(pkg.PackageName), WithVersionInRange(ver)), nil
}

func predicateForLabelDependency(value string) (OperatorPredicate, error) {
var label opregistry.LabelDependency
func predicateForRequiredLabelProperty(value string) (OperatorPredicate, error) {
var label struct {
Label string `json:"label"`
}
if err := json.Unmarshal([]byte(value), &label); err != nil {
return nil, err
}

return WithLabel(label.Label), nil
}

func apisToDependencies(apis APISet) (out []*api.Dependency, err error) {
func requiredAPIsToProperties(apis APISet) (out []*api.Property, err error) {
if len(apis) == 0 {
return
}
out = make([]*api.Dependency, 0)
out = make([]*api.Property, 0)
for a := range apis {
val, err := json.Marshal(opregistry.GVKDependency{
val, err := json.Marshal(struct {
Group string `json:"group"`
Version string `json:"version"`
Kind string `json:"kind"`
}{
Group: a.Group,
Kind: a.Kind,
Version: a.Version,
Kind: a.Kind,
})
if err != nil {
return nil, err
}
out = append(out, &api.Dependency{
Type: opregistry.GVKType,
out = append(out, &api.Property{
Type: "olm.gvk.required",
Value: string(val),
})
}
Expand All @@ -512,13 +521,13 @@ func apisToDependencies(apis APISet) (out []*api.Dependency, err error) {
return nil, nil
}

func apisToProperties(apis APISet) (out []*api.Property, err error) {
func providedAPIsToProperties(apis APISet) (out []*api.Property, err error) {
out = make([]*api.Property, 0)
for a := range apis {
val, err := json.Marshal(opregistry.GVKProperty{
Group: a.Group,
Kind: a.Kind,
Version: a.Version,
Kind: a.Kind,
})
if err != nil {
panic(err)
Expand All @@ -533,3 +542,63 @@ func apisToProperties(apis APISet) (out []*api.Property, err error) {
}
return nil, nil
}

func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) {
var result []*api.Property
for _, dependency := range dependencies {
switch dependency.Type {
case "olm.gvk":
type gvk struct {
Group string `json:"group"`
Version string `json:"version"`
Kind string `json:"kind"`
}
var vfrom gvk
if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil {
return nil, fmt.Errorf("failed to unmarshal legacy 'olm.gvk' dependency: %w", err)
}
vto := gvk{
Group: vfrom.Group,
Version: vfrom.Version,
Kind: vfrom.Kind,
}
vb, err := json.Marshal(&vto)
if err != nil {
return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err)
}
result = append(result, &api.Property{
Type: "olm.gvk.required",
Value: string(vb),
})
case "olm.package":
var vfrom struct {
PackageName string `json:"packageName"`
VersionRange string `json:"version"`
}
if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil {
return nil, fmt.Errorf("failed to unmarshal legacy 'olm.package' dependency: %w", err)
}
vto := struct {
PackageName string `json:"packageName"`
VersionRange string `json:"versionRange"`
}{
PackageName: vfrom.PackageName,
VersionRange: vfrom.VersionRange,
}
vb, err := json.Marshal(&vto)
if err != nil {
return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err)
}
result = append(result, &api.Property{
Type: "olm.package.required",
Value: string(vb),
})
case "olm.label":
result = append(result, &api.Property{
Type: "olm.label.required",
Value: dependency.Value,
})
}
}
return result, nil
}
Loading

0 comments on commit c918a85

Please sign in to comment.