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  `