github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/package-server/storage/subresources.go (about) 1 package storage 2 3 import ( 4 "context" 5 "encoding/base64" 6 "io" 7 "net/http" 8 "strconv" 9 "strings" 10 11 "k8s.io/apimachinery/pkg/runtime" 12 "k8s.io/apimachinery/pkg/runtime/schema" 13 genericreq "k8s.io/apiserver/pkg/endpoints/request" 14 "k8s.io/apiserver/pkg/registry/rest" 15 16 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators" 17 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/provider" 18 ) 19 20 // LogoStorage implements Kubernetes methods needed to provide the `packagemanifests/icon` subresource 21 type LogoStorage struct { 22 groupResource schema.GroupResource 23 prov provider.PackageManifestProvider 24 } 25 26 var _ rest.Connecter = &LogoStorage{} 27 var _ rest.StorageMetadata = &LogoStorage{} 28 29 // NewLogoStorage returns struct which implements Kubernetes methods needed to provide the `packagemanifests/icon` subresource 30 func NewLogoStorage(groupResource schema.GroupResource, prov provider.PackageManifestProvider) *LogoStorage { 31 return &LogoStorage{groupResource, prov} 32 } 33 34 // New satisfies the Storage interface 35 func (s *LogoStorage) New() runtime.Object { 36 return &operators.PackageManifest{} 37 } 38 39 // Destroy satisfies the Storage interface 40 // Performs a no-op 41 func (s *LogoStorage) Destroy() {} 42 43 // Connect satisfies the Connector interface and returns the image icon file for a given `PackageManifest` 44 func (s *LogoStorage) Connect(ctx context.Context, name string, options runtime.Object, responder rest.Responder) (http.Handler, error) { 45 var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { 46 if match := r.Header.Get("If-None-Match"); match != "" && r.URL.Query().Get("resourceVersion") != "" { 47 w.WriteHeader(http.StatusNotModified) 48 return 49 } 50 51 namespace := genericreq.NamespaceValue(ctx) 52 pkg, err := s.prov.Get(namespace, name) 53 if err != nil || pkg == nil { 54 w.WriteHeader(http.StatusNotFound) 55 return 56 } 57 imgBytes, mimeType, etag := func() ([]byte, string, string) { 58 for _, pkgChannel := range pkg.Status.Channels { 59 if pkgChannel.Name != pkg.Status.DefaultChannel { 60 continue 61 } 62 63 desc := pkgChannel.CurrentCSVDesc 64 if len(desc.Icon) == 0 { 65 break 66 } 67 68 // The first icon is call we care about 69 icon := desc.Icon[0] 70 data := icon.Base64Data 71 mimeType := icon.Mediatype 72 etag := `"` + strings.Join([]string{name, pkgChannel.Name, pkgChannel.CurrentCSV}, ".") + `"` 73 74 reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(data)) 75 imgBytes, _ := io.ReadAll(reader) 76 77 return imgBytes, mimeType, etag 78 } 79 80 return []byte(defaultIcon), "image/svg+xml", "" 81 }() 82 83 w.Header().Set("Content-Type", mimeType) 84 w.Header().Set("Content-Length", strconv.Itoa(len(imgBytes))) 85 w.Header().Set("Etag", etag) 86 w.Write(imgBytes) 87 } 88 89 return handler, nil 90 } 91 92 // NewConnectOptions satisfies the Connector interface 93 func (s *LogoStorage) NewConnectOptions() (runtime.Object, bool, string) { 94 return nil, false, "" 95 } 96 97 // ConnectMethods satisfies the Connector interface 98 func (s *LogoStorage) ConnectMethods() []string { 99 return []string{"GET"} 100 } 101 102 // ProducesMIMETypes satisfies the StorageMetadata interface and returns the supported icon image file types 103 func (s *LogoStorage) ProducesMIMETypes(verb string) []string { 104 return []string{ 105 "image/png", 106 "image/jpeg", 107 "image/svg+xml", 108 } 109 } 110 111 // ProducesObject satisfies the StorageMetadata interface 112 func (s *LogoStorage) ProducesObject(verb string) interface{} { 113 return "" 114 } 115 116 const defaultIcon string = ` 117 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 258.51 258.51"><defs><style>.cls-1{fill:#d1d1d1;}.cls-2{fill:#8d8d8f;}</style></defs><title>Asset 4</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M129.25,20A109.1,109.1,0,0,1,206.4,206.4,109.1,109.1,0,1,1,52.11,52.11,108.45,108.45,0,0,1,129.25,20m0-20h0C58.16,0,0,58.16,0,129.25H0c0,71.09,58.16,129.26,129.25,129.26h0c71.09,0,129.26-58.17,129.26-129.26h0C258.51,58.16,200.34,0,129.25,0Z"/><path class="cls-2" d="M177.54,103.41H141.66L154.9,65.76c1.25-4.4-2.33-8.76-7.21-8.76H102.93a7.32,7.32,0,0,0-7.4,6l-10,69.61c-.59,4.17,2.89,7.89,7.4,7.89h36.9L115.55,197c-1.12,4.41,2.48,8.55,7.24,8.55a7.58,7.58,0,0,0,6.47-3.48L184,113.85C186.86,109.24,183.29,103.41,177.54,103.41Z"/></g></g></svg> 118 `