github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/source_registry.go (about) 1 package resolver 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/blang/semver/v4" 11 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 12 v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" 13 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" 14 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" 15 "github.com/operator-framework/operator-registry/pkg/api" 16 "github.com/operator-framework/operator-registry/pkg/client" 17 opregistry "github.com/operator-framework/operator-registry/pkg/registry" 18 "github.com/sirupsen/logrus" 19 "k8s.io/apimachinery/pkg/labels" 20 ) 21 22 // todo: move to pkg/controller/operators/catalog 23 24 type RegistryClientProvider interface { 25 ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface 26 } 27 28 type sourceInvalidator struct { 29 m sync.Mutex 30 validChans map[cache.SourceKey]chan struct{} 31 ttl time.Duration // auto-invalidate after this ttl 32 } 33 34 func (i *sourceInvalidator) Invalidate(key cache.SourceKey) { 35 i.m.Lock() 36 defer i.m.Unlock() 37 if c, ok := i.validChans[key]; ok { 38 close(c) 39 delete(i.validChans, key) 40 } 41 } 42 43 func (i *sourceInvalidator) GetValidChannel(key cache.SourceKey) <-chan struct{} { 44 i.m.Lock() 45 defer i.m.Unlock() 46 47 if c, ok := i.validChans[key]; ok { 48 return c 49 } 50 c := make(chan struct{}) 51 i.validChans[key] = c 52 53 go func() { 54 <-time.After(i.ttl) 55 56 // be careful to avoid closing c (and panicking) after 57 // it has already been invalidated via Invalidate 58 i.m.Lock() 59 defer i.m.Unlock() 60 61 if saved := i.validChans[key]; saved == c { 62 close(c) 63 delete(i.validChans, key) 64 } 65 }() 66 67 return c 68 } 69 70 type RegistrySourceProvider struct { 71 rcp RegistryClientProvider 72 catsrcLister v1alpha1listers.CatalogSourceLister 73 logger logrus.StdLogger 74 invalidator *sourceInvalidator 75 } 76 77 func SourceProviderFromRegistryClientProvider(rcp RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.StdLogger) *RegistrySourceProvider { 78 return &RegistrySourceProvider{ 79 rcp: rcp, 80 logger: logger, 81 catsrcLister: catsrcLister, 82 invalidator: &sourceInvalidator{ 83 validChans: make(map[cache.SourceKey]chan struct{}), 84 ttl: 5 * time.Minute, 85 }, 86 } 87 } 88 89 type errorSource struct { 90 error 91 } 92 93 func (s errorSource) Snapshot(_ context.Context) (*cache.Snapshot, error) { 94 return nil, s.error 95 } 96 97 func (a *RegistrySourceProvider) Sources(namespaces ...string) map[cache.SourceKey]cache.Source { 98 result := make(map[cache.SourceKey]cache.Source) 99 100 cats := []*operatorsv1alpha1.CatalogSource{} 101 for _, ns := range namespaces { 102 catsInNamespace, err := a.catsrcLister.CatalogSources(ns).List(labels.Everything()) 103 if err != nil { 104 result[cache.SourceKey{Name: "", Namespace: ns}] = errorSource{ 105 error: fmt.Errorf("failed to list catalogsources for namespace %q: %w", ns, err), 106 } 107 return result 108 } 109 cats = append(cats, catsInNamespace...) 110 } 111 112 clients := a.rcp.ClientsForNamespaces(namespaces...) 113 for _, cat := range cats { 114 key := cache.SourceKey{Name: cat.Name, Namespace: cat.Namespace} 115 if client, ok := clients[registry.CatalogKey{Name: cat.Name, Namespace: cat.Namespace}]; ok { 116 result[key] = ®istrySource{ 117 key: key, 118 client: client, 119 logger: a.logger, 120 invalidator: a.invalidator, 121 } 122 } else { 123 result[key] = errorSource{ 124 error: fmt.Errorf("no registry client established for catalogsource %s/%s", cat.Namespace, cat.Name), 125 } 126 } 127 } 128 if len(result) == 0 { 129 return nil 130 } 131 return result 132 } 133 134 func (a *RegistrySourceProvider) Invalidate(key cache.SourceKey) { 135 a.invalidator.Invalidate(key) 136 } 137 138 type registrySource struct { 139 key cache.SourceKey 140 client client.Interface 141 logger logrus.StdLogger 142 invalidator *sourceInvalidator 143 } 144 145 func (s *registrySource) Snapshot(ctx context.Context) (*cache.Snapshot, error) { 146 // Fetching default channels this way makes many round trips 147 // -- may need to either add a new API to fetch all at once, 148 // or embed the information into Bundle. 149 packages := make(map[string]*api.Package) 150 151 it, err := s.client.ListBundles(ctx) 152 if err != nil { 153 return nil, fmt.Errorf("failed to list bundles: %w", err) 154 } 155 156 var operators []*cache.Entry 157 for b := it.Next(); b != nil; b = it.Next() { 158 p, ok := packages[b.PackageName] 159 if !ok { 160 if p, err = s.client.GetPackage(ctx, b.PackageName); err != nil { 161 s.logger.Printf("failed to retrieve default channel for bundle, continuing: %v", err) 162 continue 163 } else { 164 packages[b.PackageName] = p 165 } 166 } 167 o, err := newOperatorFromBundle(b, "", s.key, p.DefaultChannelName) 168 if err != nil { 169 s.logger.Printf("failed to construct operator from bundle, continuing: %v", err) 170 continue 171 } 172 var deprecations *cache.Deprecations 173 if p.Deprecation != nil { 174 deprecations = &cache.Deprecations{Package: &api.Deprecation{Message: fmt.Sprintf("olm.package/%s: %s", p.Name, p.Deprecation.GetMessage())}} 175 } 176 for _, c := range p.Channels { 177 if c.Name == b.ChannelName && c.Deprecation != nil { 178 if deprecations == nil { 179 deprecations = &cache.Deprecations{} 180 } 181 deprecations.Channel = &api.Deprecation{Message: fmt.Sprintf("olm.channel/%s: %s", c.Name, c.Deprecation.GetMessage())} 182 } 183 } 184 if b.Deprecation != nil { 185 if deprecations == nil { 186 deprecations = &cache.Deprecations{} 187 } 188 deprecations.Bundle = &api.Deprecation{Message: fmt.Sprintf("olm.bundle/%s: %s", b.CsvName, b.Deprecation.GetMessage())} 189 } 190 o.SourceInfo.Deprecations = deprecations 191 o.ProvidedAPIs = o.ProvidedAPIs.StripPlural() 192 o.RequiredAPIs = o.RequiredAPIs.StripPlural() 193 o.Replaces = b.Replaces 194 EnsurePackageProperty(o, b.PackageName, b.Version) 195 operators = append(operators, o) 196 } 197 if err := it.Error(); err != nil { 198 return nil, fmt.Errorf("error encountered while listing bundles: %w", err) 199 } 200 201 return &cache.Snapshot{ 202 Entries: operators, 203 Valid: s.invalidator.GetValidChannel(s.key), 204 }, nil 205 } 206 207 func EnsurePackageProperty(o *cache.Entry, name, version string) { 208 for _, p := range o.Properties { 209 if p.Type == opregistry.PackageType { 210 return 211 } 212 } 213 prop := opregistry.PackageProperty{ 214 PackageName: name, 215 Version: version, 216 } 217 bytes, err := json.Marshal(prop) 218 if err != nil { 219 return 220 } 221 o.Properties = append(o.Properties, &api.Property{ 222 Type: opregistry.PackageType, 223 Value: string(bytes), 224 }) 225 } 226 227 func newOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey cache.SourceKey, defaultChannel string) (*cache.Entry, error) { 228 parsedVersion, err := semver.ParseTolerant(bundle.Version) 229 version := &parsedVersion 230 if err != nil { 231 version = nil 232 } 233 provided := cache.APISet{} 234 for _, gvk := range bundle.ProvidedApis { 235 provided[opregistry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{} 236 } 237 required := cache.APISet{} 238 for _, gvk := range bundle.RequiredApis { 239 required[opregistry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{} 240 } 241 sourceInfo := &cache.OperatorSourceInfo{ 242 Package: bundle.PackageName, 243 Channel: bundle.ChannelName, 244 StartingCSV: startingCSV, 245 Catalog: sourceKey, 246 } 247 sourceInfo.DefaultChannel = sourceInfo.Channel == defaultChannel 248 249 // legacy support - if the api doesn't contain properties/dependencies, build them from required/provided apis 250 properties := bundle.Properties 251 if len(properties) == 0 { 252 properties, err = providedAPIsToProperties(provided) 253 if err != nil { 254 return nil, err 255 } 256 } 257 if len(bundle.Dependencies) > 0 { 258 ps, err := legacyDependenciesToProperties(bundle.Dependencies) 259 if err != nil { 260 return nil, fmt.Errorf("failed to translate legacy dependencies to properties: %w", err) 261 } 262 properties = append(properties, ps...) 263 } else { 264 ps, err := requiredAPIsToProperties(required) 265 if err != nil { 266 return nil, err 267 } 268 properties = append(properties, ps...) 269 } 270 271 o := &cache.Entry{ 272 Name: bundle.CsvName, 273 Replaces: bundle.Replaces, 274 Version: version, 275 ProvidedAPIs: provided, 276 RequiredAPIs: required, 277 SourceInfo: sourceInfo, 278 Properties: properties, 279 Skips: bundle.Skips, 280 BundlePath: bundle.BundlePath, 281 } 282 283 if r, err := semver.ParseRange(bundle.SkipRange); err == nil { 284 o.SkipRange = r 285 } 286 287 if o.BundlePath == "" { 288 // This bundle's content is embedded within the Bundle 289 // proto message, not specified via image reference. 290 o.Bundle = bundle 291 } 292 293 return o, nil 294 } 295 296 func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) { 297 var result []*api.Property 298 for _, dependency := range dependencies { 299 switch dependency.Type { 300 case "olm.gvk": 301 type gvk struct { 302 Group string `json:"group"` 303 Version string `json:"version"` 304 Kind string `json:"kind"` 305 } 306 var vfrom gvk 307 if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil { 308 return nil, fmt.Errorf("failed to unmarshal legacy 'olm.gvk' dependency: %w", err) 309 } 310 vto := gvk{ 311 Group: vfrom.Group, 312 Version: vfrom.Version, 313 Kind: vfrom.Kind, 314 } 315 vb, err := json.Marshal(&vto) 316 if err != nil { 317 return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err) 318 } 319 result = append(result, &api.Property{ 320 Type: "olm.gvk.required", 321 Value: string(vb), 322 }) 323 case "olm.package": 324 var vfrom struct { 325 PackageName string `json:"packageName"` 326 VersionRange string `json:"version"` 327 } 328 if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil { 329 return nil, fmt.Errorf("failed to unmarshal legacy 'olm.package' dependency: %w", err) 330 } 331 vto := struct { 332 PackageName string `json:"packageName"` 333 VersionRange string `json:"versionRange"` 334 }{ 335 PackageName: vfrom.PackageName, 336 VersionRange: vfrom.VersionRange, 337 } 338 vb, err := json.Marshal(&vto) 339 if err != nil { 340 return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err) 341 } 342 result = append(result, &api.Property{ 343 Type: "olm.package.required", 344 Value: string(vb), 345 }) 346 case "olm.label": 347 result = append(result, &api.Property{ 348 Type: "olm.label.required", 349 Value: dependency.Value, 350 }) 351 case "olm.constraint": 352 result = append(result, &api.Property{ 353 Type: "olm.constraint", 354 Value: dependency.Value, 355 }) 356 } 357 } 358 return result, nil 359 }