github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/source_registry_test.go (about) 1 package resolver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "testing" 7 8 "github.com/blang/semver/v4" 9 opver "github.com/operator-framework/api/pkg/lib/version" 10 "github.com/operator-framework/api/pkg/operators/v1alpha1" 11 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" 12 "github.com/operator-framework/operator-registry/pkg/api" 13 opregistry "github.com/operator-framework/operator-registry/pkg/registry" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 ) 19 20 func TestNewOperatorFromBundle(t *testing.T) { 21 version := opver.OperatorVersion{Version: semver.MustParse("0.1.0-abc")} 22 csv := v1alpha1.ClusterServiceVersion{ 23 TypeMeta: metav1.TypeMeta{ 24 Kind: v1alpha1.ClusterServiceVersionKind, 25 APIVersion: v1alpha1.GroupVersion, 26 }, 27 ObjectMeta: metav1.ObjectMeta{ 28 Name: "testCSV", 29 Namespace: "testNamespace", 30 }, 31 Spec: v1alpha1.ClusterServiceVersionSpec{ 32 Replaces: "v1", 33 CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{ 34 Owned: []v1alpha1.CRDDescription{}, 35 Required: []v1alpha1.CRDDescription{}, 36 }, 37 APIServiceDefinitions: v1alpha1.APIServiceDefinitions{ 38 Owned: []v1alpha1.APIServiceDescription{}, 39 Required: []v1alpha1.APIServiceDescription{}, 40 }, 41 Version: version, 42 }, 43 } 44 45 csvJSON, err := json.Marshal(csv) 46 require.NoError(t, err) 47 bundleNoAPIs := &api.Bundle{ 48 CsvName: "testBundle", 49 PackageName: "testPackage", 50 ChannelName: "testChannel", 51 Version: version.String(), 52 CsvJson: string(csvJSON), 53 Object: []string{string(csvJSON)}, 54 } 55 56 csv.Spec.CustomResourceDefinitions.Owned = []v1alpha1.CRDDescription{ 57 { 58 Name: "owneds.crd.group.com", 59 Version: "v1", 60 Kind: "OwnedCRD", 61 }, 62 } 63 csv.Spec.CustomResourceDefinitions.Required = []v1alpha1.CRDDescription{ 64 { 65 Name: "requireds.crd.group.com", 66 Version: "v1", 67 Kind: "RequiredCRD", 68 }, 69 } 70 csv.Spec.APIServiceDefinitions.Owned = []v1alpha1.APIServiceDescription{ 71 { 72 Name: "ownedapis", 73 Group: "apis.group.com", 74 Version: "v1", 75 Kind: "OwnedAPI", 76 }, 77 } 78 csv.Spec.APIServiceDefinitions.Required = []v1alpha1.APIServiceDescription{ 79 { 80 Name: "requiredapis", 81 Group: "apis.group.com", 82 Version: "v1", 83 Kind: "RequiredAPI", 84 }, 85 } 86 87 csvJSONWithAPIs, err := json.Marshal(csv) 88 require.NoError(t, err) 89 90 crd := v1beta1.CustomResourceDefinition{ 91 TypeMeta: metav1.TypeMeta{ 92 Kind: "CustomResourceDefinition", 93 APIVersion: "apiextensions.k8s.io/v1beta1", 94 }, 95 ObjectMeta: metav1.ObjectMeta{ 96 Name: "owneds.crd.group.com", 97 }, 98 Spec: v1beta1.CustomResourceDefinitionSpec{ 99 Group: "crd.group.com", 100 Versions: []v1beta1.CustomResourceDefinitionVersion{ 101 { 102 Name: "v1", 103 Served: true, 104 Storage: true, 105 }, 106 }, 107 Names: v1beta1.CustomResourceDefinitionNames{ 108 Plural: "owneds", 109 Singular: "owned", 110 Kind: "OwnedCRD", 111 ListKind: "OwnedCRDList", 112 }, 113 }, 114 } 115 crdJSON, err := json.Marshal(crd) 116 require.NoError(t, err) 117 118 bundleWithAPIs := &api.Bundle{ 119 CsvName: "testBundle", 120 PackageName: "testPackage", 121 ChannelName: "testChannel", 122 Version: version.String(), 123 CsvJson: string(csvJSONWithAPIs), 124 Object: []string{string(csvJSONWithAPIs), string(crdJSON)}, 125 ProvidedApis: []*api.GroupVersionKind{ 126 { 127 Group: "crd.group.com", 128 Version: "v1", 129 Kind: "OwnedCRD", 130 Plural: "owneds", 131 }, 132 { 133 Plural: "ownedapis", 134 Group: "apis.group.com", 135 Version: "v1", 136 Kind: "OwnedAPI", 137 }, 138 }, 139 RequiredApis: []*api.GroupVersionKind{ 140 { 141 Group: "crd.group.com", 142 Version: "v1", 143 Kind: "RequiredCRD", 144 Plural: "requireds", 145 }, 146 { 147 Plural: "requiredapis", 148 Group: "apis.group.com", 149 Version: "v1", 150 Kind: "RequiredAPI", 151 }, 152 }, 153 } 154 155 bundleWithPropsAndDeps := &api.Bundle{ 156 CsvName: "testBundle", 157 PackageName: "testPackage", 158 ChannelName: "testChannel", 159 Version: version.String(), 160 BundlePath: "image", 161 Properties: []*api.Property{ 162 { 163 Type: "olm.gvk", 164 Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}", 165 }, 166 { 167 Type: "olm.gvk", 168 Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}", 169 }, 170 }, 171 Dependencies: []*api.Dependency{ 172 { 173 Type: "olm.gvk", 174 Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}", 175 }, 176 { 177 Type: "olm.gvk", 178 Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}", 179 }, 180 }, 181 } 182 183 bundleWithAPIsUnextracted := &api.Bundle{ 184 CsvName: "testBundle", 185 PackageName: "testPackage", 186 ChannelName: "testChannel", 187 CsvJson: string(csvJSONWithAPIs), 188 Object: []string{string(csvJSONWithAPIs), string(crdJSON)}, 189 } 190 191 type args struct { 192 bundle *api.Bundle 193 sourceKey cache.SourceKey 194 defaultChannel string 195 } 196 tests := []struct { 197 name string 198 args args 199 want *cache.Entry 200 wantErr error 201 }{ 202 { 203 name: "BundleNoAPIs", 204 args: args{ 205 bundle: bundleNoAPIs, 206 sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 207 }, 208 want: &cache.Entry{ 209 Name: "testBundle", 210 Version: &version.Version, 211 ProvidedAPIs: cache.EmptyAPISet(), 212 RequiredAPIs: cache.EmptyAPISet(), 213 Bundle: bundleNoAPIs, 214 SourceInfo: &cache.OperatorSourceInfo{ 215 Package: "testPackage", 216 Channel: "testChannel", 217 Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 218 }, 219 }, 220 }, 221 { 222 name: "BundleWithAPIs", 223 args: args{ 224 bundle: bundleWithAPIs, 225 sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 226 }, 227 want: &cache.Entry{ 228 Name: "testBundle", 229 Version: &version.Version, 230 ProvidedAPIs: cache.APISet{ 231 opregistry.APIKey{ 232 Group: "crd.group.com", 233 Version: "v1", 234 Kind: "OwnedCRD", 235 Plural: "owneds", 236 }: struct{}{}, 237 opregistry.APIKey{ 238 Group: "apis.group.com", 239 Version: "v1", 240 Kind: "OwnedAPI", 241 Plural: "ownedapis", 242 }: struct{}{}, 243 }, 244 RequiredAPIs: cache.APISet{ 245 opregistry.APIKey{ 246 Group: "crd.group.com", 247 Version: "v1", 248 Kind: "RequiredCRD", 249 Plural: "requireds", 250 }: struct{}{}, 251 opregistry.APIKey{ 252 Group: "apis.group.com", 253 Version: "v1", 254 Kind: "RequiredAPI", 255 Plural: "requiredapis", 256 }: struct{}{}, 257 }, 258 Properties: []*api.Property{ 259 { 260 Type: "olm.gvk", 261 Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}", 262 }, 263 { 264 Type: "olm.gvk", 265 Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}", 266 }, 267 { 268 Type: "olm.gvk.required", 269 Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}", 270 }, 271 { 272 Type: "olm.gvk.required", 273 Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}", 274 }, 275 }, 276 Bundle: bundleWithAPIs, 277 SourceInfo: &cache.OperatorSourceInfo{ 278 Package: "testPackage", 279 Channel: "testChannel", 280 Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 281 }, 282 }, 283 }, 284 { 285 name: "BundleIgnoreCSV", 286 args: args{ 287 bundle: bundleWithAPIsUnextracted, 288 sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 289 }, 290 want: &cache.Entry{ 291 Name: "testBundle", 292 ProvidedAPIs: cache.EmptyAPISet(), 293 RequiredAPIs: cache.EmptyAPISet(), 294 Bundle: bundleWithAPIsUnextracted, 295 SourceInfo: &cache.OperatorSourceInfo{ 296 Package: "testPackage", 297 Channel: "testChannel", 298 Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 299 }, 300 }, 301 }, 302 { 303 name: "BundleInDefaultChannel", 304 args: args{ 305 bundle: bundleNoAPIs, 306 sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 307 defaultChannel: "testChannel", 308 }, 309 want: &cache.Entry{ 310 Name: "testBundle", 311 Version: &version.Version, 312 ProvidedAPIs: cache.EmptyAPISet(), 313 RequiredAPIs: cache.EmptyAPISet(), 314 Bundle: bundleNoAPIs, 315 SourceInfo: &cache.OperatorSourceInfo{ 316 Package: "testPackage", 317 Channel: "testChannel", 318 Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 319 DefaultChannel: true, 320 }, 321 }, 322 }, 323 { 324 name: "BundleWithPropertiesAndDependencies", 325 args: args{ 326 bundle: bundleWithPropsAndDeps, 327 sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 328 }, 329 want: &cache.Entry{ 330 Name: "testBundle", 331 Version: &version.Version, 332 ProvidedAPIs: cache.EmptyAPISet(), 333 RequiredAPIs: cache.EmptyAPISet(), 334 Properties: []*api.Property{ 335 { 336 Type: "olm.gvk", 337 Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}", 338 }, 339 { 340 Type: "olm.gvk", 341 Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}", 342 }, 343 { 344 Type: "olm.gvk.required", 345 Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}", 346 }, 347 { 348 Type: "olm.gvk.required", 349 Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}", 350 }, 351 }, 352 BundlePath: bundleWithPropsAndDeps.BundlePath, 353 SourceInfo: &cache.OperatorSourceInfo{ 354 Package: "testPackage", 355 Channel: "testChannel", 356 Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"}, 357 }, 358 }, 359 }, 360 } 361 for _, tt := range tests { 362 t.Run(tt.name, func(t *testing.T) { 363 got, err := newOperatorFromBundle(tt.args.bundle, "", tt.args.sourceKey, tt.args.defaultChannel) 364 require.Equal(t, tt.wantErr, err) 365 requirePropertiesEqual(t, tt.want.Properties, got.Properties) 366 tt.want.Properties, got.Properties = nil, nil 367 require.Equal(t, tt.want, got) 368 }) 369 } 370 } 371 372 func TestNewOperatorFromBundleStripsPluralRequiredAndProvidedAPIKeys(t *testing.T) { 373 key := cache.SourceKey{Namespace: "testnamespace", Name: "testname"} 374 o, err := newOperatorFromBundle(&api.Bundle{ 375 CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name), 376 ProvidedApis: []*api.GroupVersionKind{{ 377 Group: "g", 378 Version: "v1", 379 Kind: "K", 380 Plural: "ks", 381 }}, 382 RequiredApis: []*api.GroupVersionKind{{ 383 Group: "g2", 384 Version: "v2", 385 Kind: "K2", 386 Plural: "ks2", 387 }}, 388 }, "", key, "") 389 390 assert.NoError(t, err) 391 assert.Equal(t, "K.v1.g", o.ProvidedAPIs.String()) 392 assert.Equal(t, "K2.v2.g2", o.RequiredAPIs.String()) 393 }