...

Source file src/github.com/redhat-developer/odo/pkg/kclient/operators.go

Documentation: github.com/redhat-developer/odo/pkg/kclient

     1  package kclient
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"k8s.io/apimachinery/pkg/api/meta"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  	"k8s.io/apimachinery/pkg/runtime/schema"
    14  
    15  	"github.com/go-openapi/spec"
    16  	olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
    17  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    18  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/klog"
    20  )
    21  
    22  // IsCSVSupported checks if resource of type service binding request present on the cluster
    23  func (c *Client) IsCSVSupported() (bool, error) {
    24  	return c.IsResourceSupported("operators.coreos.com", "v1alpha1", "clusterserviceversions")
    25  }
    26  
    27  // ListClusterServiceVersions returns a list of CSVs in the cluster
    28  // It is equivalent to doing `oc get csvs` using oc cli
    29  func (c *Client) ListClusterServiceVersions() (*olm.ClusterServiceVersionList, error) {
    30  	klog.V(3).Infof("Fetching list of operators installed in cluster")
    31  	csvs, err := c.OperatorClient.ClusterServiceVersions(c.Namespace).List(context.TODO(), v1.ListOptions{})
    32  	if err != nil {
    33  		if kerrors.IsNotFound(err) {
    34  			return &olm.ClusterServiceVersionList{}, nil
    35  		}
    36  		return &olm.ClusterServiceVersionList{}, err
    37  	}
    38  	return csvs, nil
    39  }
    40  
    41  // GetCustomResourcesFromCSV returns a list of CRs provided by an operator/CSV.
    42  func (c *Client) GetCustomResourcesFromCSV(csv *olm.ClusterServiceVersion) *[]olm.CRDDescription {
    43  	// we will return a list of CRs owned by the csv
    44  	return &csv.Spec.CustomResourceDefinitions.Owned
    45  }
    46  
    47  // GetCSVWithCR returns the CSV (Operator) that contains the CR (service)
    48  func (c *Client) GetCSVWithCR(name string) (*olm.ClusterServiceVersion, error) {
    49  	csvs, err := c.ListClusterServiceVersions()
    50  	if err != nil {
    51  		return &olm.ClusterServiceVersion{}, fmt.Errorf("unable to list services: %w", err)
    52  	}
    53  
    54  	for _, csv := range csvs.Items {
    55  		clusterServiceVersion := csv
    56  		for _, cr := range *c.GetCustomResourcesFromCSV(&clusterServiceVersion) {
    57  			if cr.Kind == name {
    58  				return &csv, nil
    59  			}
    60  		}
    61  	}
    62  	return &olm.ClusterServiceVersion{}, fmt.Errorf("could not find any Operator containing requested CR: %s", name)
    63  }
    64  
    65  // GetResourceSpecDefinition returns the OpenAPI v2 definition of the Kubernetes resource of a given group/version/kind
    66  func (c *Client) GetResourceSpecDefinition(group, version, kind string) (*spec.Schema, error) {
    67  	data, err := c.KubeClient.Discovery().RESTClient().Get().AbsPath("/openapi/v2").SetHeader("Accept", "application/json").Do(context.TODO()).Raw()
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	return getResourceSpecDefinitionFromSwagger(data, group, version, kind)
    72  }
    73  
    74  // getResourceSpecDefinitionFromSwagger returns the OpenAPI v2 definition of the Kubernetes resource of a given group/version/kind, for a given swagger data
    75  func getResourceSpecDefinitionFromSwagger(data []byte, group, version, kind string) (*spec.Schema, error) {
    76  	schema := new(spec.Schema)
    77  	err := json.Unmarshal(data, schema)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	var crd spec.Schema
    83  	found := false
    84  loopDefinitions:
    85  	for _, definition := range schema.Definitions {
    86  		extensions := definition.Extensions
    87  		gvkI, ok := extensions["x-kubernetes-group-version-kind"]
    88  		if !ok {
    89  			continue
    90  		}
    91  		// The concrete type of this extension is expected to be an array of interface{}
    92  		// If not, we ignore it
    93  		gvkA, ok := gvkI.([]interface{})
    94  		if !ok {
    95  			continue
    96  		}
    97  
    98  		for i := range gvkA {
    99  			// The concrete type of each element is expected to be a map[string]interface{}
   100  			// If not, we ignore it
   101  			gvk, ok := gvkA[i].(map[string]interface{})
   102  			if !ok {
   103  				continue
   104  			}
   105  			gvkGroup := gvk["group"].(string)
   106  			gvkVersion := gvk["version"].(string)
   107  			gvkKind := gvk["kind"].(string)
   108  			if strings.HasSuffix(group, gvkGroup) && version == gvkVersion && kind == gvkKind {
   109  				crd = definition
   110  				found = true
   111  				break loopDefinitions
   112  			}
   113  		}
   114  
   115  	}
   116  	if !found {
   117  		return nil, errors.New("no definition found")
   118  	}
   119  
   120  	spec, ok := crd.Properties["spec"]
   121  	if ok {
   122  		return &spec, nil
   123  	}
   124  	return nil, nil
   125  }
   126  
   127  // toOpenAPISpec transforms Spec descriptors from a CRD description to an OpenAPI schema
   128  func toOpenAPISpec(repr *olm.CRDDescription) *spec.Schema {
   129  	if len(repr.SpecDescriptors) == 0 {
   130  		return nil
   131  	}
   132  	schema := new(spec.Schema).Typed("object", "")
   133  	schema.AdditionalProperties = &spec.SchemaOrBool{
   134  		Allows: false,
   135  	}
   136  	for _, param := range repr.SpecDescriptors {
   137  		addParam(schema, param)
   138  	}
   139  	return schema
   140  }
   141  
   142  // addParam adds a Spec Descriptor parameter to an OpenAPI schema
   143  func addParam(schema *spec.Schema, param olm.SpecDescriptor) {
   144  	parts := strings.SplitN(param.Path, ".", 2)
   145  	if len(parts) == 1 {
   146  		child := spec.StringProperty()
   147  		if len(param.XDescriptors) == 1 {
   148  			switch param.XDescriptors[0] {
   149  			case "urn:alm:descriptor:com.tectonic.ui:podCount":
   150  				child = spec.Int32Property()
   151  				// TODO(feloy) more cases, based on
   152  				// - https://github.com/openshift/console/blob/master/frontend/packages/operator-lifecycle-manager/src/components/descriptors/reference/reference.md
   153  				// - https://docs.google.com/document/d/17Tdmpu4R6pA5UC4LumyJ2EP6AcotMWM127Jy728hYCk
   154  			}
   155  		}
   156  		child = child.WithTitle(param.DisplayName).WithDescription(param.Description)
   157  		schema.SetProperty(parts[0], *child)
   158  	} else {
   159  		var child *spec.Schema
   160  		if _, ok := schema.Properties[parts[0]]; ok {
   161  			c := schema.Properties[parts[0]]
   162  			child = &c
   163  		} else {
   164  			child = new(spec.Schema).Typed("object", "")
   165  		}
   166  		param.Path = parts[1]
   167  		addParam(child, param)
   168  		schema.SetProperty(parts[0], *child)
   169  	}
   170  }
   171  
   172  // GetRestMappingFromUnstructured returns rest mappings from unstructured data
   173  func (c *Client) GetRestMappingFromUnstructured(u unstructured.Unstructured) (*meta.RESTMapping, error) {
   174  	gvk := u.GroupVersionKind()
   175  	return c.restmapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   176  }
   177  
   178  func (c *Client) GetRestMappingFromGVK(gvk schema.GroupVersionKind) (*meta.RESTMapping, error) {
   179  	return c.restmapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   180  }
   181  
   182  func (c *Client) GetGVKFromGVR(gvr schema.GroupVersionResource) (schema.GroupVersionKind, error) {
   183  	return c.restmapper.KindFor(gvr)
   184  }
   185  
   186  func (c *Client) GetGVRFromGVK(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
   187  	mapping, err := c.restmapper.RESTMapping(gvk.GroupKind())
   188  	if err != nil {
   189  		return schema.GroupVersionResource{}, err
   190  	}
   191  	return mapping.Resource, nil
   192  }
   193  
   194  // GetOperatorGVRList creates a slice of rest mappings that are provided by Operators (CSV)
   195  func (c *Client) GetOperatorGVRList() ([]meta.RESTMapping, error) {
   196  	var operatorGVRList []meta.RESTMapping
   197  
   198  	// ignoring the error because
   199  	csvs, err := c.ListClusterServiceVersions()
   200  	if err != nil {
   201  		return operatorGVRList, err
   202  	}
   203  	for _, c := range csvs.Items {
   204  		owned := c.Spec.CustomResourceDefinitions.Owned
   205  		for i := range owned {
   206  			operatorGVRList = append(operatorGVRList, meta.RESTMapping{
   207  				Resource: GetGVRFromCR(&owned[i]),
   208  			})
   209  		}
   210  	}
   211  	return operatorGVRList, nil
   212  }
   213  
   214  func ConvertUnstructuredToResource(u unstructured.Unstructured, obj interface{}) error {
   215  	return runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), obj)
   216  }
   217  
   218  func ConvertUnstructuredListToResource(u unstructured.UnstructuredList, obj interface{}) error {
   219  	return runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), obj)
   220  }
   221  

View as plain text