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 }