k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/openapiconv/convert.go (about)

     1  /*
     2  Copyright 2022 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 openapiconv
    18  
    19  import (
    20  	"strings"
    21  
    22  	klog "k8s.io/klog/v2"
    23  	builderutil "k8s.io/kube-openapi/pkg/builder3/util"
    24  	"k8s.io/kube-openapi/pkg/spec3"
    25  	"k8s.io/kube-openapi/pkg/validation/spec"
    26  )
    27  
    28  var OpenAPIV2DefPrefix = "#/definitions/"
    29  var OpenAPIV3DefPrefix = "#/components/schemas/"
    30  
    31  // ConvertV2ToV3 converts an OpenAPI V2 object into V3.
    32  // Certain references may be shared between the V2 and V3 objects in the conversion.
    33  func ConvertV2ToV3(v2Spec *spec.Swagger) *spec3.OpenAPI {
    34  	v3Spec := &spec3.OpenAPI{
    35  		Version:      "3.0.0",
    36  		Info:         v2Spec.Info,
    37  		ExternalDocs: ConvertExternalDocumentation(v2Spec.ExternalDocs),
    38  		Paths:        ConvertPaths(v2Spec.Paths),
    39  		Components:   ConvertComponents(v2Spec.SecurityDefinitions, v2Spec.Definitions, v2Spec.Responses, v2Spec.Produces),
    40  	}
    41  
    42  	return v3Spec
    43  }
    44  
    45  func ConvertExternalDocumentation(v2ED *spec.ExternalDocumentation) *spec3.ExternalDocumentation {
    46  	if v2ED == nil {
    47  		return nil
    48  	}
    49  	return &spec3.ExternalDocumentation{
    50  		ExternalDocumentationProps: spec3.ExternalDocumentationProps{
    51  			Description: v2ED.Description,
    52  			URL:         v2ED.URL,
    53  		},
    54  	}
    55  }
    56  
    57  func ConvertComponents(v2SecurityDefinitions spec.SecurityDefinitions, v2Definitions spec.Definitions, v2Responses map[string]spec.Response, produces []string) *spec3.Components {
    58  	components := &spec3.Components{}
    59  
    60  	if v2Definitions != nil {
    61  		components.Schemas = make(map[string]*spec.Schema)
    62  	}
    63  	for s, schema := range v2Definitions {
    64  		components.Schemas[s] = ConvertSchema(&schema)
    65  	}
    66  	if v2SecurityDefinitions != nil {
    67  		components.SecuritySchemes = make(spec3.SecuritySchemes)
    68  	}
    69  	for s, securityScheme := range v2SecurityDefinitions {
    70  		components.SecuritySchemes[s] = ConvertSecurityScheme(securityScheme)
    71  	}
    72  	if v2Responses != nil {
    73  		components.Responses = make(map[string]*spec3.Response)
    74  	}
    75  	for r, response := range v2Responses {
    76  		components.Responses[r] = ConvertResponse(&response, produces)
    77  	}
    78  
    79  	return components
    80  }
    81  
    82  func ConvertSchema(v2Schema *spec.Schema) *spec.Schema {
    83  	if v2Schema == nil {
    84  		return nil
    85  	}
    86  	v3Schema := spec.Schema{
    87  		VendorExtensible:   v2Schema.VendorExtensible,
    88  		SchemaProps:        v2Schema.SchemaProps,
    89  		SwaggerSchemaProps: v2Schema.SwaggerSchemaProps,
    90  		ExtraProps:         v2Schema.ExtraProps,
    91  	}
    92  
    93  	if refString := v2Schema.Ref.String(); refString != "" {
    94  		if idx := strings.Index(refString, OpenAPIV2DefPrefix); idx != -1 {
    95  			v3Schema.Ref = spec.MustCreateRef(OpenAPIV3DefPrefix + refString[idx+len(OpenAPIV2DefPrefix):])
    96  		} else {
    97  			klog.Errorf("Error: Swagger V2 Ref %s does not contain #/definitions\n", refString)
    98  		}
    99  	}
   100  
   101  	if v2Schema.Properties != nil {
   102  		v3Schema.Properties = make(map[string]spec.Schema)
   103  		for key, property := range v2Schema.Properties {
   104  			v3Schema.Properties[key] = *ConvertSchema(&property)
   105  		}
   106  	}
   107  	if v2Schema.Items != nil {
   108  		v3Schema.Items = &spec.SchemaOrArray{
   109  			Schema:  ConvertSchema(v2Schema.Items.Schema),
   110  			Schemas: ConvertSchemaList(v2Schema.Items.Schemas),
   111  		}
   112  	}
   113  
   114  	if v2Schema.AdditionalProperties != nil {
   115  		v3Schema.AdditionalProperties = &spec.SchemaOrBool{
   116  			Schema: ConvertSchema(v2Schema.AdditionalProperties.Schema),
   117  			Allows: v2Schema.AdditionalProperties.Allows,
   118  		}
   119  	}
   120  	if v2Schema.AdditionalItems != nil {
   121  		v3Schema.AdditionalItems = &spec.SchemaOrBool{
   122  			Schema: ConvertSchema(v2Schema.AdditionalItems.Schema),
   123  			Allows: v2Schema.AdditionalItems.Allows,
   124  		}
   125  	}
   126  
   127  	return builderutil.WrapRefs(&v3Schema)
   128  }
   129  
   130  func ConvertSchemaList(v2SchemaList []spec.Schema) []spec.Schema {
   131  	if v2SchemaList == nil {
   132  		return nil
   133  	}
   134  	v3SchemaList := []spec.Schema{}
   135  	for _, s := range v2SchemaList {
   136  		v3SchemaList = append(v3SchemaList, *ConvertSchema(&s))
   137  	}
   138  	return v3SchemaList
   139  }
   140  
   141  func ConvertSecurityScheme(v2securityScheme *spec.SecurityScheme) *spec3.SecurityScheme {
   142  	if v2securityScheme == nil {
   143  		return nil
   144  	}
   145  	securityScheme := &spec3.SecurityScheme{
   146  		VendorExtensible: v2securityScheme.VendorExtensible,
   147  		SecuritySchemeProps: spec3.SecuritySchemeProps{
   148  			Description: v2securityScheme.Description,
   149  			Type:        v2securityScheme.Type,
   150  			Name:        v2securityScheme.Name,
   151  			In:          v2securityScheme.In,
   152  		},
   153  	}
   154  
   155  	if v2securityScheme.Flow != "" {
   156  		securityScheme.Flows = make(map[string]*spec3.OAuthFlow)
   157  		securityScheme.Flows[v2securityScheme.Flow] = &spec3.OAuthFlow{
   158  			OAuthFlowProps: spec3.OAuthFlowProps{
   159  				AuthorizationUrl: v2securityScheme.AuthorizationURL,
   160  				TokenUrl:         v2securityScheme.TokenURL,
   161  				Scopes:           v2securityScheme.Scopes,
   162  			},
   163  		}
   164  	}
   165  	return securityScheme
   166  }
   167  
   168  func ConvertPaths(v2Paths *spec.Paths) *spec3.Paths {
   169  	if v2Paths == nil {
   170  		return nil
   171  	}
   172  	paths := &spec3.Paths{
   173  		VendorExtensible: v2Paths.VendorExtensible,
   174  	}
   175  
   176  	if v2Paths.Paths != nil {
   177  		paths.Paths = make(map[string]*spec3.Path)
   178  	}
   179  	for k, v := range v2Paths.Paths {
   180  		paths.Paths[k] = ConvertPathItem(v)
   181  	}
   182  	return paths
   183  }
   184  
   185  func ConvertPathItem(v2pathItem spec.PathItem) *spec3.Path {
   186  	path := &spec3.Path{
   187  		Refable: v2pathItem.Refable,
   188  		PathProps: spec3.PathProps{
   189  			Get:     ConvertOperation(v2pathItem.Get),
   190  			Put:     ConvertOperation(v2pathItem.Put),
   191  			Post:    ConvertOperation(v2pathItem.Post),
   192  			Delete:  ConvertOperation(v2pathItem.Delete),
   193  			Options: ConvertOperation(v2pathItem.Options),
   194  			Head:    ConvertOperation(v2pathItem.Head),
   195  			Patch:   ConvertOperation(v2pathItem.Patch),
   196  		},
   197  		VendorExtensible: v2pathItem.VendorExtensible,
   198  	}
   199  	for _, param := range v2pathItem.Parameters {
   200  		path.Parameters = append(path.Parameters, ConvertParameter(param))
   201  	}
   202  	return path
   203  }
   204  
   205  func ConvertOperation(v2Operation *spec.Operation) *spec3.Operation {
   206  	if v2Operation == nil {
   207  		return nil
   208  	}
   209  	operation := &spec3.Operation{
   210  		VendorExtensible: v2Operation.VendorExtensible,
   211  		OperationProps: spec3.OperationProps{
   212  			Description:  v2Operation.Description,
   213  			ExternalDocs: ConvertExternalDocumentation(v2Operation.OperationProps.ExternalDocs),
   214  			Tags:         v2Operation.Tags,
   215  			Summary:      v2Operation.Summary,
   216  			Deprecated:   v2Operation.Deprecated,
   217  			OperationId:  v2Operation.ID,
   218  		},
   219  	}
   220  
   221  	for _, param := range v2Operation.Parameters {
   222  		if param.ParamProps.Name == "body" && param.ParamProps.Schema != nil {
   223  			operation.OperationProps.RequestBody = &spec3.RequestBody{
   224  				RequestBodyProps: spec3.RequestBodyProps{},
   225  			}
   226  			if v2Operation.Consumes != nil {
   227  				operation.RequestBody.Content = make(map[string]*spec3.MediaType)
   228  			}
   229  			for _, consumer := range v2Operation.Consumes {
   230  				operation.RequestBody.Content[consumer] = &spec3.MediaType{
   231  					MediaTypeProps: spec3.MediaTypeProps{
   232  						Schema: ConvertSchema(param.ParamProps.Schema),
   233  					},
   234  				}
   235  			}
   236  		} else {
   237  			operation.Parameters = append(operation.Parameters, ConvertParameter(param))
   238  		}
   239  	}
   240  
   241  	operation.Responses = &spec3.Responses{ResponsesProps: spec3.ResponsesProps{
   242  		Default: ConvertResponse(v2Operation.Responses.Default, v2Operation.Produces),
   243  	},
   244  		VendorExtensible: v2Operation.Responses.VendorExtensible,
   245  	}
   246  
   247  	if v2Operation.Responses.StatusCodeResponses != nil {
   248  		operation.Responses.StatusCodeResponses = make(map[int]*spec3.Response)
   249  	}
   250  	for k, v := range v2Operation.Responses.StatusCodeResponses {
   251  		operation.Responses.StatusCodeResponses[k] = ConvertResponse(&v, v2Operation.Produces)
   252  	}
   253  	return operation
   254  }
   255  
   256  func ConvertResponse(v2Response *spec.Response, produces []string) *spec3.Response {
   257  	if v2Response == nil {
   258  		return nil
   259  	}
   260  	response := &spec3.Response{
   261  		Refable:          ConvertRefableResponse(v2Response.Refable),
   262  		VendorExtensible: v2Response.VendorExtensible,
   263  		ResponseProps: spec3.ResponseProps{
   264  			Description: v2Response.Description,
   265  		},
   266  	}
   267  
   268  	if v2Response.Schema != nil {
   269  		if produces != nil {
   270  			response.Content = make(map[string]*spec3.MediaType)
   271  		}
   272  		for _, producer := range produces {
   273  			response.ResponseProps.Content[producer] = &spec3.MediaType{
   274  				MediaTypeProps: spec3.MediaTypeProps{
   275  					Schema: ConvertSchema(v2Response.Schema),
   276  				},
   277  			}
   278  		}
   279  	}
   280  	return response
   281  }
   282  
   283  func ConvertParameter(v2Param spec.Parameter) *spec3.Parameter {
   284  	param := &spec3.Parameter{
   285  		Refable:          ConvertRefableParameter(v2Param.Refable),
   286  		VendorExtensible: v2Param.VendorExtensible,
   287  		ParameterProps: spec3.ParameterProps{
   288  			Name:            v2Param.Name,
   289  			Description:     v2Param.Description,
   290  			In:              v2Param.In,
   291  			Required:        v2Param.Required,
   292  			Schema:          ConvertSchema(v2Param.Schema),
   293  			AllowEmptyValue: v2Param.AllowEmptyValue,
   294  		},
   295  	}
   296  	// Convert SimpleSchema into Schema
   297  	if param.Schema == nil {
   298  		param.Schema = &spec.Schema{
   299  			SchemaProps: spec.SchemaProps{
   300  				Type:        []string{v2Param.Type},
   301  				Format:      v2Param.Format,
   302  				UniqueItems: v2Param.UniqueItems,
   303  			},
   304  		}
   305  	}
   306  
   307  	return param
   308  }
   309  
   310  func ConvertRefableParameter(refable spec.Refable) spec.Refable {
   311  	if refable.Ref.String() != "" {
   312  		return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/parameters/", "#/components/parameters/", 1))}
   313  	}
   314  	return refable
   315  }
   316  
   317  func ConvertRefableResponse(refable spec.Refable) spec.Refable {
   318  	if refable.Ref.String() != "" {
   319  		return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/responses/", "#/components/responses/", 1))}
   320  	}
   321  	return refable
   322  }