...

Source file src/k8s.io/client-go/openapi3/root.go

Documentation: k8s.io/client-go/openapi3

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package openapi3
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/client-go/openapi"
    28  	"k8s.io/kube-openapi/pkg/spec3"
    29  )
    30  
    31  // Root interface defines functions implemented against the root
    32  // OpenAPI V3 document. The root OpenAPI V3 document maps the
    33  // API Server relative url for all GroupVersions to the relative
    34  // url for the OpenAPI relative url. Example for single GroupVersion
    35  // apps/v1:
    36  //
    37  //	"apis/apps/v1": {
    38  //	    "ServerRelativeURL": "/openapi/v3/apis/apps/v1?hash=<HASH>"
    39  //	}
    40  type Root interface {
    41  	// GroupVersions returns every GroupVersion for which there is an
    42  	// OpenAPI V3 GroupVersion document. Returns an error for problems
    43  	// retrieving or parsing the OpenAPI V3 root document.
    44  	GroupVersions() ([]schema.GroupVersion, error)
    45  	// GVSpec returns the specification for all the resources in a
    46  	// GroupVersion as a pointer to a spec3.OpenAPI struct.
    47  	// Returns an error for problems retrieving or parsing the root
    48  	// document or GroupVersion OpenAPI V3 document.
    49  	GVSpec(gv schema.GroupVersion) (*spec3.OpenAPI, error)
    50  	// GVSpecAsMap returns the specification for all the resources in a
    51  	// GroupVersion as unstructured bytes. Returns an error for
    52  	// problems retrieving or parsing the root or GroupVersion
    53  	// OpenAPI V3 document.
    54  	GVSpecAsMap(gv schema.GroupVersion) (map[string]interface{}, error)
    55  }
    56  
    57  // root implements the Root interface, and encapsulates the
    58  // fields to retrieve, store the parsed OpenAPI V3 root document.
    59  type root struct {
    60  	// OpenAPI client to retrieve the OpenAPI V3 documents.
    61  	client openapi.Client
    62  }
    63  
    64  // Validate root implements the Root interface.
    65  var _ Root = &root{}
    66  
    67  // NewRoot returns a structure implementing the Root interface,
    68  // created with the passed rest client.
    69  func NewRoot(client openapi.Client) Root {
    70  	return &root{client: client}
    71  }
    72  
    73  func (r *root) GroupVersions() ([]schema.GroupVersion, error) {
    74  	paths, err := r.client.Paths()
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	// Example GroupVersion API path: "apis/apps/v1"
    79  	gvs := make([]schema.GroupVersion, 0, len(paths))
    80  	for gvAPIPath := range paths {
    81  		gv, err := pathToGroupVersion(gvAPIPath)
    82  		if err != nil {
    83  			// Ignore paths which do not parse to GroupVersion
    84  			continue
    85  		}
    86  		gvs = append(gvs, gv)
    87  	}
    88  	// Sort GroupVersions alphabetically
    89  	sort.Slice(gvs, func(i, j int) bool {
    90  		return gvs[i].String() < gvs[j].String()
    91  	})
    92  	return gvs, nil
    93  }
    94  
    95  func (r *root) GVSpec(gv schema.GroupVersion) (*spec3.OpenAPI, error) {
    96  	openAPISchemaBytes, err := r.retrieveGVBytes(gv)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	// Unmarshal the downloaded Group/Version bytes into the spec3.OpenAPI struct.
   101  	var parsedV3Schema spec3.OpenAPI
   102  	err = json.Unmarshal(openAPISchemaBytes, &parsedV3Schema)
   103  	return &parsedV3Schema, err
   104  }
   105  
   106  func (r *root) GVSpecAsMap(gv schema.GroupVersion) (map[string]interface{}, error) {
   107  	gvOpenAPIBytes, err := r.retrieveGVBytes(gv)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	// GroupVersion bytes into unstructured map[string] -> empty interface.
   112  	var gvMap map[string]interface{}
   113  	err = json.Unmarshal(gvOpenAPIBytes, &gvMap)
   114  	return gvMap, err
   115  }
   116  
   117  // retrieveGVBytes returns the schema for a passed GroupVersion as an
   118  // unstructured slice of bytes or an error if there is a problem downloading
   119  // or if the passed GroupVersion is not supported.
   120  func (r *root) retrieveGVBytes(gv schema.GroupVersion) ([]byte, error) {
   121  	paths, err := r.client.Paths()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	apiPath := gvToAPIPath(gv)
   126  	gvOpenAPI, found := paths[apiPath]
   127  	if !found {
   128  		return nil, &GroupVersionNotFoundError{gv: gv}
   129  	}
   130  	return gvOpenAPI.Schema(runtime.ContentTypeJSON)
   131  }
   132  
   133  // gvToAPIPath maps the passed GroupVersion to a relative api
   134  // server url. Example:
   135  //
   136  //	GroupVersion{Group: "apps", Version: "v1"} -> "apis/apps/v1".
   137  func gvToAPIPath(gv schema.GroupVersion) string {
   138  	var resourcePath string
   139  	if len(gv.Group) == 0 {
   140  		resourcePath = fmt.Sprintf("api/%s", gv.Version)
   141  	} else {
   142  		resourcePath = fmt.Sprintf("apis/%s/%s", gv.Group, gv.Version)
   143  	}
   144  	return resourcePath
   145  }
   146  
   147  // pathToGroupVersion is a helper function parsing the passed relative
   148  // url into a GroupVersion.
   149  //
   150  //	Example: apis/apps/v1 -> GroupVersion{Group: "apps", Version: "v1"}
   151  //	Example: api/v1 -> GroupVersion{Group: "", Version: "v1"}
   152  func pathToGroupVersion(path string) (schema.GroupVersion, error) {
   153  	var gv schema.GroupVersion
   154  	parts := strings.Split(path, "/")
   155  	if len(parts) < 2 {
   156  		return gv, fmt.Errorf("Unable to parse api relative path: %s", path)
   157  	}
   158  	apiPrefix := parts[0]
   159  	if apiPrefix == "apis" {
   160  		// Example: apis/apps (without version)
   161  		if len(parts) < 3 {
   162  			return gv, fmt.Errorf("Group without Version not allowed")
   163  		}
   164  		gv.Group = parts[1]
   165  		gv.Version = parts[2]
   166  	} else if apiPrefix == "api" {
   167  		gv.Version = parts[1]
   168  	} else {
   169  		return gv, fmt.Errorf("Unable to parse api relative path: %s", path)
   170  	}
   171  	return gv, nil
   172  }
   173  
   174  // Encapsulates GroupVersion not found as one of the paths
   175  // at OpenAPI V3 endpoint.
   176  type GroupVersionNotFoundError struct {
   177  	gv schema.GroupVersion
   178  }
   179  
   180  func (r *GroupVersionNotFoundError) Error() string {
   181  	return fmt.Sprintf("GroupVersion (%v) not found as OpenAPI V3 path", r.gv)
   182  }
   183  

View as plain text