github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/util_test.go (about)

     1  package resolver
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/operator-framework/operator-registry/pkg/api"
     9  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    10  	"github.com/stretchr/testify/require"
    11  	appsv1 "k8s.io/api/apps/v1"
    12  	corev1 "k8s.io/api/core/v1"
    13  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    16  	"k8s.io/apimachinery/pkg/runtime"
    17  
    18  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    19  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    20  )
    21  
    22  func csv(name, replaces string, ownedCRDs, requiredCRDs, ownedAPIServices, requiredAPIServices cache.APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *v1alpha1.ClusterServiceVersion {
    23  	var singleInstance = int32(1)
    24  	strategy := v1alpha1.StrategyDetailsDeployment{
    25  		Permissions:        permissions,
    26  		ClusterPermissions: clusterPermissions,
    27  		DeploymentSpecs: []v1alpha1.StrategyDeploymentSpec{
    28  			{
    29  				Name: name,
    30  				Spec: appsv1.DeploymentSpec{
    31  					Selector: &metav1.LabelSelector{
    32  						MatchLabels: map[string]string{
    33  							"app": name,
    34  						},
    35  					},
    36  					Replicas: &singleInstance,
    37  					Template: corev1.PodTemplateSpec{
    38  						ObjectMeta: metav1.ObjectMeta{
    39  							Labels: map[string]string{
    40  								"app": name,
    41  							},
    42  						},
    43  						Spec: corev1.PodSpec{
    44  							ServiceAccountName: "sa",
    45  							Containers: []corev1.Container{
    46  								{
    47  									Name:  name + "-c1",
    48  									Image: "nginx:1.7.9",
    49  									Ports: []corev1.ContainerPort{
    50  										{
    51  											ContainerPort: 80,
    52  										},
    53  									},
    54  								},
    55  							},
    56  						},
    57  					},
    58  				},
    59  			},
    60  		},
    61  	}
    62  
    63  	installStrategy := v1alpha1.NamedInstallStrategy{
    64  		StrategyName: v1alpha1.InstallStrategyNameDeployment,
    65  		StrategySpec: strategy,
    66  	}
    67  
    68  	requiredCRDDescs := make([]v1alpha1.CRDDescription, 0)
    69  	for crd := range requiredCRDs {
    70  		requiredCRDDescs = append(requiredCRDDescs, v1alpha1.CRDDescription{Name: crd.Plural + "." + crd.Group, Version: crd.Version, Kind: crd.Kind})
    71  	}
    72  
    73  	ownedCRDDescs := make([]v1alpha1.CRDDescription, 0)
    74  	for crd := range ownedCRDs {
    75  		ownedCRDDescs = append(ownedCRDDescs, v1alpha1.CRDDescription{Name: crd.Plural + "." + crd.Group, Version: crd.Version, Kind: crd.Kind})
    76  	}
    77  
    78  	requiredAPIDescs := make([]v1alpha1.APIServiceDescription, 0)
    79  	for api := range requiredAPIServices {
    80  		requiredAPIDescs = append(requiredAPIDescs, v1alpha1.APIServiceDescription{Name: api.Plural, Group: api.Group, Version: api.Version, Kind: api.Kind})
    81  	}
    82  
    83  	ownedAPIDescs := make([]v1alpha1.APIServiceDescription, 0)
    84  	for api := range ownedAPIServices {
    85  		ownedAPIDescs = append(ownedAPIDescs, v1alpha1.APIServiceDescription{Name: api.Plural, Group: api.Group, Version: api.Version, Kind: api.Kind, DeploymentName: name, ContainerPort: 80})
    86  	}
    87  
    88  	return &v1alpha1.ClusterServiceVersion{
    89  		TypeMeta: metav1.TypeMeta{
    90  			Kind:       v1alpha1.ClusterServiceVersionKind,
    91  			APIVersion: v1alpha1.SchemeGroupVersion.String(),
    92  		},
    93  		ObjectMeta: metav1.ObjectMeta{
    94  			Name: name,
    95  		},
    96  		Spec: v1alpha1.ClusterServiceVersionSpec{
    97  			Replaces:        replaces,
    98  			InstallStrategy: installStrategy,
    99  			CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
   100  				Owned:    ownedCRDDescs,
   101  				Required: requiredCRDDescs,
   102  			},
   103  			APIServiceDefinitions: v1alpha1.APIServiceDefinitions{
   104  				Owned:    ownedAPIDescs,
   105  				Required: requiredAPIDescs,
   106  			},
   107  		},
   108  	}
   109  }
   110  
   111  func crd(key opregistry.APIKey) *v1beta1.CustomResourceDefinition {
   112  	return &v1beta1.CustomResourceDefinition{
   113  		TypeMeta: metav1.TypeMeta{
   114  			Kind:       "CustomResourceDefinition",
   115  			APIVersion: v1beta1.SchemeGroupVersion.String(),
   116  		},
   117  		ObjectMeta: metav1.ObjectMeta{
   118  			Name: key.Plural + "." + key.Group,
   119  		},
   120  		Spec: v1beta1.CustomResourceDefinitionSpec{
   121  			Group: key.Group,
   122  			Versions: []v1beta1.CustomResourceDefinitionVersion{
   123  				{
   124  					Name:    key.Version,
   125  					Storage: true,
   126  					Served:  true,
   127  				},
   128  			},
   129  			Names: v1beta1.CustomResourceDefinitionNames{
   130  				Kind:   key.Kind,
   131  				Plural: key.Plural,
   132  			},
   133  		},
   134  	}
   135  }
   136  
   137  func u(object runtime.Object) *unstructured.Unstructured {
   138  	unst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(object)
   139  	if err != nil {
   140  		panic(err)
   141  	}
   142  	return &unstructured.Unstructured{Object: unst}
   143  }
   144  
   145  func apiSetToGVK(crds, apis cache.APISet) (out []*api.GroupVersionKind) {
   146  	out = make([]*api.GroupVersionKind, 0)
   147  	for a := range crds {
   148  		out = append(out, &api.GroupVersionKind{
   149  			Group:   a.Group,
   150  			Version: a.Version,
   151  			Kind:    a.Kind,
   152  			Plural:  a.Plural,
   153  		})
   154  	}
   155  	for a := range apis {
   156  		out = append(out, &api.GroupVersionKind{
   157  			Group:   a.Group,
   158  			Version: a.Version,
   159  			Kind:    a.Kind,
   160  			Plural:  a.Plural,
   161  		})
   162  	}
   163  	return
   164  }
   165  
   166  func packageNameToProperty(packageName, version string) (out *api.Property) {
   167  	val, err := json.Marshal(opregistry.PackageProperty{
   168  		PackageName: packageName,
   169  		Version:     version,
   170  	})
   171  	if err != nil {
   172  		panic(err)
   173  	}
   174  
   175  	return &api.Property{
   176  		Type:  opregistry.PackageType,
   177  		Value: string(val),
   178  	}
   179  }
   180  
   181  type bundleOpt func(*api.Bundle)
   182  
   183  func withSkipRange(skipRange string) bundleOpt {
   184  	return func(b *api.Bundle) {
   185  		b.SkipRange = skipRange
   186  	}
   187  }
   188  
   189  func withSkips(skips []string) bundleOpt {
   190  	return func(b *api.Bundle) {
   191  		b.Skips = skips
   192  	}
   193  }
   194  
   195  func withVersion(version string) bundleOpt {
   196  	return func(b *api.Bundle) {
   197  		b.Version = version
   198  		props := b.GetProperties()
   199  		for i, p := range props {
   200  			if p.Type == opregistry.PackageType {
   201  				props[i] = packageNameToProperty(b.PackageName, b.Version)
   202  			}
   203  		}
   204  		b.Properties = props
   205  	}
   206  }
   207  
   208  func bundle(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices cache.APISet, opts ...bundleOpt) *api.Bundle {
   209  	csvJSON, err := json.Marshal(csv(name, replaces, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices, nil, nil))
   210  	if err != nil {
   211  		panic(err)
   212  	}
   213  
   214  	objs := []string{string(csvJSON)}
   215  	for p := range providedCRDs {
   216  		crdJSON, err := json.Marshal(crd(p))
   217  		if err != nil {
   218  			panic(err)
   219  		}
   220  		objs = append(objs, string(crdJSON))
   221  	}
   222  
   223  	b := &api.Bundle{
   224  		CsvName:      name,
   225  		PackageName:  pkg,
   226  		ChannelName:  channel,
   227  		CsvJson:      string(csvJSON),
   228  		Object:       objs,
   229  		Version:      "0.0.0",
   230  		ProvidedApis: apiSetToGVK(providedCRDs, providedAPIServices),
   231  		RequiredApis: apiSetToGVK(requiredCRDs, requiredAPIServices),
   232  		Replaces:     replaces,
   233  		Dependencies: apiSetToDependencies(requiredCRDs, requiredAPIServices),
   234  		Properties: append(apiSetToProperties(providedCRDs, providedAPIServices, false),
   235  			packageNameToProperty(pkg, "0.0.0"),
   236  		),
   237  	}
   238  	for _, f := range opts {
   239  		f(b)
   240  	}
   241  	return b
   242  }
   243  
   244  func stripManifests(bundle *api.Bundle) *api.Bundle {
   245  	bundle.CsvJson = ""
   246  	bundle.Object = nil
   247  	return bundle
   248  }
   249  
   250  func withBundleObject(bundle *api.Bundle, obj *unstructured.Unstructured) *api.Bundle {
   251  	j, err := json.Marshal(obj)
   252  	if err != nil {
   253  		panic(err)
   254  	}
   255  	bundle.Object = append(bundle.Object, string(j))
   256  	return bundle
   257  }
   258  
   259  func withBundlePath(bundle *api.Bundle, path string) *api.Bundle {
   260  	bundle.BundlePath = path
   261  	return bundle
   262  }
   263  
   264  func bundleWithPermissions(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices cache.APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *api.Bundle {
   265  	csvJSON, err := json.Marshal(csv(name, replaces, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices, permissions, clusterPermissions))
   266  	if err != nil {
   267  		panic(err)
   268  	}
   269  
   270  	objs := []string{string(csvJSON)}
   271  	for p := range providedCRDs {
   272  		crdJSON, err := json.Marshal(crd(p))
   273  		if err != nil {
   274  			panic(err)
   275  		}
   276  		objs = append(objs, string(crdJSON))
   277  	}
   278  
   279  	return &api.Bundle{
   280  		CsvName:      name,
   281  		PackageName:  pkg,
   282  		ChannelName:  channel,
   283  		CsvJson:      string(csvJSON),
   284  		Object:       objs,
   285  		ProvidedApis: apiSetToGVK(providedCRDs, providedAPIServices),
   286  		RequiredApis: apiSetToGVK(requiredCRDs, requiredAPIServices),
   287  	}
   288  }
   289  
   290  func requirePropertiesEqual(t *testing.T, a, b []*api.Property) {
   291  	type Property struct {
   292  		Type  string
   293  		Value interface{}
   294  	}
   295  	nice := func(in *api.Property) Property {
   296  		var i interface{}
   297  		if err := json.Unmarshal([]byte(in.Value), &i); err != nil {
   298  			t.Fatalf("property value %q could not be unmarshaled as json: %s", in.Value, err)
   299  		}
   300  		return Property{
   301  			Type:  in.Type,
   302  			Value: i,
   303  		}
   304  	}
   305  	var l, r []Property
   306  	for _, p := range a {
   307  		l = append(l, nice(p))
   308  	}
   309  	for _, p := range b {
   310  		r = append(r, nice(p))
   311  	}
   312  	require.ElementsMatch(t, l, r)
   313  }
   314  
   315  func apiSetToProperties(crds, apis cache.APISet, deprecated bool) []*api.Property {
   316  	var out []*api.Property
   317  	for a := range crds {
   318  		val, err := json.Marshal(opregistry.GVKProperty{
   319  			Group:   a.Group,
   320  			Kind:    a.Kind,
   321  			Version: a.Version,
   322  		})
   323  		if err != nil {
   324  			panic(err)
   325  		}
   326  		out = append(out, &api.Property{
   327  			Type:  opregistry.GVKType,
   328  			Value: string(val),
   329  		})
   330  	}
   331  	for a := range apis {
   332  		val, err := json.Marshal(opregistry.GVKProperty{
   333  			Group:   a.Group,
   334  			Kind:    a.Kind,
   335  			Version: a.Version,
   336  		})
   337  		if err != nil {
   338  			panic(err)
   339  		}
   340  		out = append(out, &api.Property{
   341  			Type:  opregistry.GVKType,
   342  			Value: string(val),
   343  		})
   344  	}
   345  	if deprecated {
   346  		val, err := json.Marshal(opregistry.DeprecatedProperty{})
   347  		if err != nil {
   348  			panic(err)
   349  		}
   350  		out = append(out, &api.Property{
   351  			Type:  opregistry.DeprecatedType,
   352  			Value: string(val),
   353  		})
   354  	}
   355  	sort.Slice(out, func(i, j int) bool {
   356  		return out[i].Type < out[j].Type || out[i].Value < out[j].Value
   357  	})
   358  	return out
   359  }
   360  
   361  func apiSetToDependencies(crds, apis cache.APISet) (out []*api.Dependency) {
   362  	if len(crds)+len(apis) == 0 {
   363  		return nil
   364  	}
   365  	out = make([]*api.Dependency, 0)
   366  	for a := range crds {
   367  		val, err := json.Marshal(opregistry.GVKDependency{
   368  			Group:   a.Group,
   369  			Kind:    a.Kind,
   370  			Version: a.Version,
   371  		})
   372  		if err != nil {
   373  			panic(err)
   374  		}
   375  		out = append(out, &api.Dependency{
   376  			Type:  opregistry.GVKType,
   377  			Value: string(val),
   378  		})
   379  	}
   380  	for a := range apis {
   381  		val, err := json.Marshal(opregistry.GVKDependency{
   382  			Group:   a.Group,
   383  			Kind:    a.Kind,
   384  			Version: a.Version,
   385  		})
   386  		if err != nil {
   387  			panic(err)
   388  		}
   389  		out = append(out, &api.Dependency{
   390  			Type:  opregistry.GVKType,
   391  			Value: string(val),
   392  		})
   393  	}
   394  	if len(out) == 0 {
   395  		return nil
   396  	}
   397  	return
   398  }