mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Improvements on OpenAPI spec generation:
- Generating models using go2idl library (no reflection anymore) - Remove dependencies on go-restful/swagger - Generate one swagger.json file for each web-service - Bugfix: fixed a bug in trie implementation
This commit is contained in:
		@@ -35,7 +35,7 @@ SHELL := /bin/bash
 | 
			
		||||
# This rule collects all the generated file sets into a single rule.  Other
 | 
			
		||||
# rules should depend on this to ensure generated files are rebuilt.
 | 
			
		||||
.PHONY: generated_files
 | 
			
		||||
generated_files: gen_deepcopy gen_conversion
 | 
			
		||||
generated_files: gen_deepcopy gen_conversion gen_openapi
 | 
			
		||||
 | 
			
		||||
# Code-generation logic.
 | 
			
		||||
#
 | 
			
		||||
@@ -202,10 +202,10 @@ DEEPCOPY_GEN := $(BIN_DIR)/deepcopy-gen
 | 
			
		||||
ifeq ($(DBG_MAKEFILE),1)
 | 
			
		||||
    $(warning ***** finding all +k8s:deepcopy-gen tags)
 | 
			
		||||
endif
 | 
			
		||||
DEEPCOPY_DIRS := $(shell                               \
 | 
			
		||||
DEEPCOPY_DIRS := $(shell                                             \
 | 
			
		||||
    grep --color=never -l '+k8s:deepcopy-gen=' $(ALL_K8S_TAG_FILES)  \
 | 
			
		||||
        | xargs -n1 dirname                            \
 | 
			
		||||
        | sort -u                                      \
 | 
			
		||||
        | xargs -n1 dirname                                          \
 | 
			
		||||
        | sort -u                                                    \
 | 
			
		||||
)
 | 
			
		||||
DEEPCOPY_FILES := $(addsuffix /$(DEEPCOPY_FILENAME), $(DEEPCOPY_DIRS))
 | 
			
		||||
 | 
			
		||||
@@ -285,6 +285,107 @@ $(DEEPCOPY_GEN):
 | 
			
		||||
	hack/make-rules/build.sh cmd/libs/go2idl/deepcopy-gen
 | 
			
		||||
	touch $@
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Open-api generation
 | 
			
		||||
#
 | 
			
		||||
# Any package that wants open-api functions generated must include a
 | 
			
		||||
# comment-tag in column 0 of one file of the form:
 | 
			
		||||
#     // +k8s:openapi-gen=true
 | 
			
		||||
#
 | 
			
		||||
# The result file, in each pkg, of open-api generation.
 | 
			
		||||
OPENAPI_BASENAME := $(GENERATED_FILE_PREFIX)openapi
 | 
			
		||||
OPENAPI_FILENAME := $(OPENAPI_BASENAME).go
 | 
			
		||||
 | 
			
		||||
# The tool used to generate open apis.
 | 
			
		||||
OPENAPI_GEN := $(BIN_DIR)/openapi-gen
 | 
			
		||||
 | 
			
		||||
# Find all the directories that request open-api generation.
 | 
			
		||||
ifeq ($(DBG_MAKEFILE),1)
 | 
			
		||||
    $(warning ***** finding all +k8s:openapi-gen tags)
 | 
			
		||||
endif
 | 
			
		||||
OPENAPI_DIRS := $(shell                                             \
 | 
			
		||||
    grep --color=never -l '+k8s:openapi-gen=' $(ALL_K8S_TAG_FILES)  \
 | 
			
		||||
        | xargs -n1 dirname                                         \
 | 
			
		||||
        | sort -u                                                   \
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
OPENAPI_FILES := $(addsuffix /$(OPENAPI_FILENAME), $(OPENAPI_DIRS))
 | 
			
		||||
 | 
			
		||||
# This rule aggregates the set of files to generate and then generates them all
 | 
			
		||||
# in a single run of the tool.
 | 
			
		||||
.PHONY: gen_openapi
 | 
			
		||||
gen_openapi: $(OPENAPI_FILES)
 | 
			
		||||
	if [[ -f $(META_DIR)/$(OPENAPI_GEN).todo ]]; then                   \
 | 
			
		||||
	    ./hack/run-in-gopath.sh $(OPENAPI_GEN)                          \
 | 
			
		||||
	        --v $(KUBE_VERBOSE)                                         \
 | 
			
		||||
	        -i $$(cat $(META_DIR)/$(OPENAPI_GEN).todo | paste -sd, -)   \
 | 
			
		||||
	        -O $(OPENAPI_BASENAME);                                     \
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
# For each dir in OPENAPI_DIRS, this establishes a dependency between the
 | 
			
		||||
# output file and the input files that should trigger a rebuild.
 | 
			
		||||
#
 | 
			
		||||
# Note that this is a deps-only statement, not a full rule (see below).  This
 | 
			
		||||
# has to be done in a distinct step because wildcards don't work in static
 | 
			
		||||
# pattern rules.
 | 
			
		||||
#
 | 
			
		||||
# The '$(eval)' is needed because this has a different RHS for each LHS, and
 | 
			
		||||
# would otherwise produce results that make can't parse.
 | 
			
		||||
#
 | 
			
		||||
# We depend on the $(GOFILES_META).stamp to detect when the set of input files
 | 
			
		||||
# has changed.  This allows us to detect deleted input files.
 | 
			
		||||
$(foreach dir, $(OPENAPI_DIRS), $(eval                                     \
 | 
			
		||||
    $(dir)/$(OPENAPI_FILENAME): $(META_DIR)/$(dir)/$(GOFILES_META).stamp   \
 | 
			
		||||
                                 $(gofiles__$(dir))                        \
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
# Unilaterally remove any leftovers from previous runs.
 | 
			
		||||
$(shell rm -f $(META_DIR)/$(OPENAPI_GEN)*.todo)
 | 
			
		||||
 | 
			
		||||
# How to regenerate open-api code.  We need to collect these up and trigger one
 | 
			
		||||
# single run to generate definition for all types.
 | 
			
		||||
$(OPENAPI_FILES): $(OPENAPI_GEN)
 | 
			
		||||
	mkdir -p $$(dirname $(META_DIR)/$(OPENAPI_GEN))
 | 
			
		||||
	echo $(PRJ_SRC_PATH)/$(@D) >> $(META_DIR)/$(OPENAPI_GEN).todo
 | 
			
		||||
 | 
			
		||||
# This calculates the dependencies for the generator tool, so we only rebuild
 | 
			
		||||
# it when needed.  It is PHONY so that it always runs, but it only updates the
 | 
			
		||||
# file if the contents have actually changed.  We 'sinclude' this later.
 | 
			
		||||
.PHONY: $(META_DIR)/$(OPENAPI_GEN).mk
 | 
			
		||||
$(META_DIR)/$(OPENAPI_GEN).mk:
 | 
			
		||||
	mkdir -p $(@D);                                        \
 | 
			
		||||
	(echo -n "$(OPENAPI_GEN): ";                           \
 | 
			
		||||
	 DIRECT=$$(go list -f '{{.Dir}} {{.Dir}}/*.go'         \
 | 
			
		||||
	     ./cmd/libs/go2idl/openapi-gen);                   \
 | 
			
		||||
	 INDIRECT=$$(go list                                   \
 | 
			
		||||
	     -f '{{range .Deps}}{{.}}{{"\n"}}{{end}}'          \
 | 
			
		||||
	     ./cmd/libs/go2idl/openapi-gen                     \
 | 
			
		||||
	     | grep --color=never "^$(PRJ_SRC_PATH)"           \
 | 
			
		||||
	     | sed 's|^$(PRJ_SRC_PATH)|./|'                    \
 | 
			
		||||
	     | xargs go list -f '{{.Dir}} {{.Dir}}/*.go');     \
 | 
			
		||||
	 echo $$DIRECT $$INDIRECT                              \
 | 
			
		||||
	     | sed 's/ / \\=,/g'                               \
 | 
			
		||||
	     | tr '=,' '\n\t';                                 \
 | 
			
		||||
	) | sed "s|$$(pwd -P)/||" > $@.tmp;                    \
 | 
			
		||||
	cmp -s $@.tmp $@ || cat $@.tmp > $@ && rm -f $@.tmp
 | 
			
		||||
 | 
			
		||||
# Include dependency info for the generator tool.  This will cause the rule of
 | 
			
		||||
# the same name to be considered and if it is updated, make will restart.
 | 
			
		||||
sinclude $(META_DIR)/$(OPENAPI_GEN).mk
 | 
			
		||||
 | 
			
		||||
# How to build the generator tool.  The deps for this are defined in
 | 
			
		||||
# the $(OPENAPI_GEN).mk, above.
 | 
			
		||||
#
 | 
			
		||||
# A word on the need to touch: This rule might trigger if, for example, a
 | 
			
		||||
# non-Go file was added or deleted from a directory on which this depends.
 | 
			
		||||
# This target needs to be reconsidered, but Go realizes it doesn't actually
 | 
			
		||||
# have to be rebuilt.  In that case, make will forever see the dependency as
 | 
			
		||||
# newer than the binary, and try to rebuild it over and over.  So we touch it,
 | 
			
		||||
# and make is happy.
 | 
			
		||||
$(OPENAPI_GEN):
 | 
			
		||||
	hack/make-rules/build.sh cmd/libs/go2idl/openapi-gen
 | 
			
		||||
	touch $@
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Conversion generation
 | 
			
		||||
#
 | 
			
		||||
@@ -315,11 +416,11 @@ CONVERSIONS_META := conversions.mk
 | 
			
		||||
ifeq ($(DBG_MAKEFILE),1)
 | 
			
		||||
    $(warning ***** finding all +k8s:conversion-gen tags)
 | 
			
		||||
endif
 | 
			
		||||
CONVERSION_DIRS := $(shell                                \
 | 
			
		||||
CONVERSION_DIRS := $(shell                                              \
 | 
			
		||||
    grep --color=never '^// *+k8s:conversion-gen=' $(ALL_K8S_TAG_FILES) \
 | 
			
		||||
        | cut -f1 -d:                                     \
 | 
			
		||||
        | xargs -n1 dirname                               \
 | 
			
		||||
        | sort -u                                         \
 | 
			
		||||
        | cut -f1 -d:                                                   \
 | 
			
		||||
        | xargs -n1 dirname                                             \
 | 
			
		||||
        | sort -u                                                       \
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONVERSION_FILES := $(addsuffix /$(CONVERSION_FILENAME), $(CONVERSION_DIRS))
 | 
			
		||||
@@ -362,11 +463,11 @@ $(foreach dir, $(CONVERSION_DIRS), $(eval           \
 | 
			
		||||
$(foreach dir, $(CONVERSION_DIRS),  \
 | 
			
		||||
    $(META_DIR)/$(dir)/$(CONVERSIONS_META)):
 | 
			
		||||
	TAGS=$$(grep --color=never -h '^// *+k8s:conversion-gen=' $</*.go   \
 | 
			
		||||
	    | cut -f2- -d=                                    \
 | 
			
		||||
	    | sed 's|$(PRJ_SRC_PATH)/||');                    \
 | 
			
		||||
	mkdir -p $(@D);                                       \
 | 
			
		||||
	echo "conversions__$< := $$(echo $${TAGS})" >$@.tmp;  \
 | 
			
		||||
	cmp -s $@.tmp $@ || touch $@.stamp;                   \
 | 
			
		||||
	    | cut -f2- -d=                                                  \
 | 
			
		||||
	    | sed 's|$(PRJ_SRC_PATH)/||');                                  \
 | 
			
		||||
	mkdir -p $(@D);                                                     \
 | 
			
		||||
	echo "conversions__$< := $$(echo $${TAGS})" >$@.tmp;                \
 | 
			
		||||
	cmp -s $@.tmp $@ || touch $@.stamp;                                 \
 | 
			
		||||
	mv $@.tmp $@
 | 
			
		||||
 | 
			
		||||
# Include any deps files as additional Makefile rules.  This triggers make to
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								cmd/libs/go2idl/openapi-gen/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								cmd/libs/go2idl/openapi-gen/README
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
# Generate OpenAPI definitions
 | 
			
		||||
 | 
			
		||||
- To generate definition for a specific type or package add "+k8s:openapi-gen=true" tag to the type/package comment lines.
 | 
			
		||||
- To exclude a type or a member from a tagged package/type, add "+k8s:openapi-gen=false" tag to the comment lines.
 | 
			
		||||
							
								
								
									
										105
									
								
								cmd/libs/go2idl/openapi-gen/generators/common/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								cmd/libs/go2idl/openapi-gen/generators/common/common.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import "github.com/go-openapi/spec"
 | 
			
		||||
 | 
			
		||||
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
 | 
			
		||||
type OpenAPIDefinition struct {
 | 
			
		||||
	Schema       spec.Schema
 | 
			
		||||
	Dependencies []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenAPIDefinitions is collection of all definitions.
 | 
			
		||||
type OpenAPIDefinitions map[string]OpenAPIDefinition
 | 
			
		||||
 | 
			
		||||
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
 | 
			
		||||
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
 | 
			
		||||
// GetOpenAPITypeFormat for more information about trade-offs of using this interface or GetOpenAPITypeFormat method when
 | 
			
		||||
// possible.
 | 
			
		||||
type OpenAPIDefinitionGetter interface {
 | 
			
		||||
	OpenAPIDefinition() *OpenAPIDefinition
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
 | 
			
		||||
// two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type
 | 
			
		||||
// comment (the comment that is added before type definition) will be lost. The spec will still have the property
 | 
			
		||||
// comment. The second way is to implement OpenAPIDefinitionGetter interface. That function can customize the spec (so
 | 
			
		||||
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
 | 
			
		||||
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
 | 
			
		||||
// Example:
 | 
			
		||||
// type Sample struct {
 | 
			
		||||
//      ...
 | 
			
		||||
//      // port of the server
 | 
			
		||||
//      port IntOrString
 | 
			
		||||
//      ...
 | 
			
		||||
// }
 | 
			
		||||
// // IntOrString documentation...
 | 
			
		||||
// type IntOrString { ... }
 | 
			
		||||
//
 | 
			
		||||
// Adding IntOrString to this function:
 | 
			
		||||
// "port" : {
 | 
			
		||||
//           format:      "string",
 | 
			
		||||
//           type:        "int-or-string",
 | 
			
		||||
//           Description: "port of the server"
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// Implement OpenAPIDefinitionGetter for IntOrString:
 | 
			
		||||
//
 | 
			
		||||
// "port" : {
 | 
			
		||||
//           $Ref:    "#/definitions/IntOrString"
 | 
			
		||||
//           Description: "port of the server"
 | 
			
		||||
// }
 | 
			
		||||
// ...
 | 
			
		||||
// definitions:
 | 
			
		||||
// {
 | 
			
		||||
//           "IntOrString": {
 | 
			
		||||
//                     format:      "string",
 | 
			
		||||
//                     type:        "int-or-string",
 | 
			
		||||
//                     Description: "IntOrString documentation..."    // new
 | 
			
		||||
//           }
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
func GetOpenAPITypeFormat(typeName string) (string, string) {
 | 
			
		||||
	schemaTypeFormatMap := map[string][]string{
 | 
			
		||||
		"uint":      {"integer", "int32"},
 | 
			
		||||
		"uint8":     {"integer", "byte"},
 | 
			
		||||
		"uint16":    {"integer", "int32"},
 | 
			
		||||
		"uint32":    {"integer", "int64"},
 | 
			
		||||
		"uint64":    {"integer", "int64"},
 | 
			
		||||
		"int":       {"integer", "int32"},
 | 
			
		||||
		"int8":      {"integer", "byte"},
 | 
			
		||||
		"int16":     {"integer", "int32"},
 | 
			
		||||
		"int32":     {"integer", "int32"},
 | 
			
		||||
		"int64":     {"integer", "int64"},
 | 
			
		||||
		"byte":      {"integer", "byte"},
 | 
			
		||||
		"float64":   {"number", "double"},
 | 
			
		||||
		"float32":   {"number", "float"},
 | 
			
		||||
		"bool":      {"boolean", ""},
 | 
			
		||||
		"time.Time": {"string", "date-time"},
 | 
			
		||||
		"string":    {"string", ""},
 | 
			
		||||
		"integer":   {"integer", ""},
 | 
			
		||||
		"number":    {"number", ""},
 | 
			
		||||
		"boolean":   {"boolean", ""},
 | 
			
		||||
		"[]byte":    {"string", "byte"}, // base64 encoded characters
 | 
			
		||||
	}
 | 
			
		||||
	mapped, ok := schemaTypeFormatMap[typeName]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", ""
 | 
			
		||||
	}
 | 
			
		||||
	return mapped[0], mapped[1]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								cmd/libs/go2idl/openapi-gen/generators/common/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								cmd/libs/go2idl/openapi-gen/generators/common/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// package common holds shared codes and types between open API code generator and spec generator.
 | 
			
		||||
package common
 | 
			
		||||
							
								
								
									
										519
									
								
								cmd/libs/go2idl/openapi-gen/generators/openapi.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								cmd/libs/go2idl/openapi-gen/generators/openapi.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,519 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package generators
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/args"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/generator"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/namer"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/types"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/sets"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// This is the comment tag that carries parameters for open API generation.
 | 
			
		||||
const tagName = "k8s:openapi-gen"
 | 
			
		||||
 | 
			
		||||
// Known values for the tag.
 | 
			
		||||
const (
 | 
			
		||||
	tagValueTrue  = "true"
 | 
			
		||||
	tagValueFalse = "false"
 | 
			
		||||
	// Should only be used only for test
 | 
			
		||||
	tagTargetType = "target"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func hasOpenAPITagValue(comments []string, value string) bool {
 | 
			
		||||
	tagValues := types.ExtractCommentTags("+", comments)[tagName]
 | 
			
		||||
	if tagValues == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	for _, val := range tagValues {
 | 
			
		||||
		if val == value {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NameSystems returns the name system used by the generators in this package.
 | 
			
		||||
func NameSystems() namer.NameSystems {
 | 
			
		||||
	return namer.NameSystems{
 | 
			
		||||
		"raw": namer.NewRawNamer("", nil),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultNameSystem returns the default name system for ordering the types to be
 | 
			
		||||
// processed by the generators in this package.
 | 
			
		||||
func DefaultNameSystem() string {
 | 
			
		||||
	return "raw"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
 | 
			
		||||
	boilerplate, err := arguments.LoadGoBoilerplate()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.Fatalf("Failed loading boilerplate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	inputs := sets.NewString(context.Inputs...)
 | 
			
		||||
	header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
 | 
			
		||||
	header = append(header, []byte(
 | 
			
		||||
		`
 | 
			
		||||
// This file was autogenerated by openapi-gen. Do not edit it manually!
 | 
			
		||||
 | 
			
		||||
`)...)
 | 
			
		||||
 | 
			
		||||
	targets := []*types.Type{}
 | 
			
		||||
	for i := range inputs {
 | 
			
		||||
		glog.V(5).Infof("considering pkg %q", i)
 | 
			
		||||
		pkg, ok := context.Universe[i]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// If the input had no Go files, for example.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for _, t := range pkg.Types {
 | 
			
		||||
			if hasOpenAPITagValue(t.CommentLines, tagTargetType) {
 | 
			
		||||
				glog.V(5).Infof("target type : %q", t)
 | 
			
		||||
				targets = append(targets, t)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	switch len(targets) {
 | 
			
		||||
	case 0:
 | 
			
		||||
		// If no target package found, that means the generated file in target package is up to date
 | 
			
		||||
		// and build excluded the target package.
 | 
			
		||||
		return generator.Packages{}
 | 
			
		||||
	case 1:
 | 
			
		||||
		pkg := context.Universe[targets[0].Name.Package]
 | 
			
		||||
		return generator.Packages{&generator.DefaultPackage{
 | 
			
		||||
			PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0],
 | 
			
		||||
			PackagePath: pkg.Path,
 | 
			
		||||
			HeaderText:  header,
 | 
			
		||||
			GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
 | 
			
		||||
				return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, targets[0], context)}
 | 
			
		||||
			},
 | 
			
		||||
			FilterFunc: func(c *generator.Context, t *types.Type) bool {
 | 
			
		||||
				// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
 | 
			
		||||
				if strings.HasPrefix(t.Name.Name, "codecSelfer") {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
				pkg := context.Universe.Package(t.Name.Package)
 | 
			
		||||
				if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
 | 
			
		||||
					return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
 | 
			
		||||
				}
 | 
			
		||||
				if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
				return false
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		glog.Fatalf("Duplicate target type found: %v", targets)
 | 
			
		||||
	}
 | 
			
		||||
	return generator.Packages{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	specPackagePath          = "github.com/go-openapi/spec"
 | 
			
		||||
	openAPICommonPackagePath = "k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// openApiGen produces a file with auto-generated OpenAPI functions.
 | 
			
		||||
type openAPIGen struct {
 | 
			
		||||
	generator.DefaultGen
 | 
			
		||||
	// TargetType is the type that will get OpenAPIDefinitions method returning all definitions.
 | 
			
		||||
	targetType *types.Type
 | 
			
		||||
	imports    namer.ImportTracker
 | 
			
		||||
	context    *generator.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewOpenAPIGen(sanitizedName string, targetType *types.Type, context *generator.Context) generator.Generator {
 | 
			
		||||
	return &openAPIGen{
 | 
			
		||||
		DefaultGen: generator.DefaultGen{
 | 
			
		||||
			OptionalName: sanitizedName,
 | 
			
		||||
		},
 | 
			
		||||
		imports:    generator.NewImportTracker(),
 | 
			
		||||
		targetType: targetType,
 | 
			
		||||
		context:    context,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
 | 
			
		||||
	// Have the raw namer for this file track what it imports.
 | 
			
		||||
	return namer.NameSystems{
 | 
			
		||||
		"raw": namer.NewRawNamer(g.targetType.Name.Package, g.imports),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
 | 
			
		||||
	// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
 | 
			
		||||
	if strings.HasPrefix(t.Name.Name, "codecSelfer") {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) isOtherPackage(pkg string) bool {
 | 
			
		||||
	if pkg == g.targetType.Name.Package {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if strings.HasSuffix(pkg, "\""+g.targetType.Name.Package+"\"") {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) Imports(c *generator.Context) []string {
 | 
			
		||||
	importLines := []string{}
 | 
			
		||||
	for _, singleImport := range g.imports.ImportLines() {
 | 
			
		||||
		importLines = append(importLines, singleImport)
 | 
			
		||||
	}
 | 
			
		||||
	return importLines
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func argsFromType(t *types.Type) generator.Args {
 | 
			
		||||
	return generator.Args{
 | 
			
		||||
		"type":               t,
 | 
			
		||||
		"OpenAPIDefinitions": types.Ref(openAPICommonPackagePath, "OpenAPIDefinitions"),
 | 
			
		||||
		"OpenAPIDefinition":  types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
 | 
			
		||||
		"SpecSchemaType":     types.Ref(specPackagePath, "Schema"),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
 | 
			
		||||
	sw := generator.NewSnippetWriter(w, c, "$", "$")
 | 
			
		||||
	sw.Do("func (_ $.type|raw$) OpenAPIDefinitions() *$.OpenAPIDefinitions|raw$ {\n", argsFromType(g.targetType))
 | 
			
		||||
	sw.Do("return &$.OpenAPIDefinitions|raw${\n", argsFromType(nil))
 | 
			
		||||
	return sw.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
 | 
			
		||||
	sw := generator.NewSnippetWriter(w, c, "$", "$")
 | 
			
		||||
	sw.Do("}\n}\n", nil)
 | 
			
		||||
	return sw.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
 | 
			
		||||
	glog.V(5).Infof("generating for type %v", t)
 | 
			
		||||
	sw := generator.NewSnippetWriter(w, c, "$", "$")
 | 
			
		||||
	err := newOpenAPITypeWriter(sw).generate(t)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return sw.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getJsonTags(m *types.Member) []string {
 | 
			
		||||
	jsonTag := reflect.StructTag(m.Tags).Get("json")
 | 
			
		||||
	if jsonTag == "" {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Split(jsonTag, ",")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getReferableName(m *types.Member) string {
 | 
			
		||||
	jsonTags := getJsonTags(m)
 | 
			
		||||
	if len(jsonTags) > 0 {
 | 
			
		||||
		if jsonTags[0] == "-" {
 | 
			
		||||
			return ""
 | 
			
		||||
		} else {
 | 
			
		||||
			return jsonTags[0]
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return m.Name
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func optionIndex(s, optionName string) int {
 | 
			
		||||
	ret := 0
 | 
			
		||||
	for s != "" {
 | 
			
		||||
		var next string
 | 
			
		||||
		i := strings.Index(s, ",")
 | 
			
		||||
		if i >= 0 {
 | 
			
		||||
			s, next = s[:i], s[i+1:]
 | 
			
		||||
		}
 | 
			
		||||
		if s == optionName {
 | 
			
		||||
			return ret
 | 
			
		||||
		}
 | 
			
		||||
		s = next
 | 
			
		||||
		ret++
 | 
			
		||||
	}
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isPropertyRequired(m *types.Member) bool {
 | 
			
		||||
	// A property is required if it does not have omitempty value in its json tag (documentation and implementation
 | 
			
		||||
	// of json package requires omitempty to be at location 1 or higher.
 | 
			
		||||
	// TODO: Move optional field definition from tags to comments.
 | 
			
		||||
	return optionIndex(reflect.StructTag(m.Tags).Get("json"), "omitempty") < 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type openAPITypeWriter struct {
 | 
			
		||||
	*generator.SnippetWriter
 | 
			
		||||
	refTypes               map[string]*types.Type
 | 
			
		||||
	GetDefinitionInterface *types.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
 | 
			
		||||
	return openAPITypeWriter{
 | 
			
		||||
		SnippetWriter: sw,
 | 
			
		||||
		refTypes:      map[string]*types.Type{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hasOpenAPIDefinitionMethod(t *types.Type) bool {
 | 
			
		||||
	for mn, mt := range t.Methods {
 | 
			
		||||
		if mn != "OpenAPIDefinition" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		r := mt.Signature.Results[0]
 | 
			
		||||
		if r.Name.Name != "OpenAPIDefinition" || r.Name.Package != openAPICommonPackagePath {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
 | 
			
		||||
func typeShortName(t *types.Type) string {
 | 
			
		||||
	return filepath.Base(t.Name.Package) + "." + t.Name.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generate(t *types.Type) error {
 | 
			
		||||
	// Only generate for struct type and ignore the rest
 | 
			
		||||
	switch t.Kind {
 | 
			
		||||
	case types.Struct:
 | 
			
		||||
		args := argsFromType(t)
 | 
			
		||||
		g.Do("\"$.$\": ", typeShortName(t))
 | 
			
		||||
		if hasOpenAPIDefinitionMethod(t) {
 | 
			
		||||
			g.Do("$.type|raw${}.OpenAPIDefinition(),", args)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
 | 
			
		||||
		g.generateDescription(t.CommentLines)
 | 
			
		||||
		g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
 | 
			
		||||
		required := []string{}
 | 
			
		||||
		for _, m := range t.Members {
 | 
			
		||||
			if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			name := getReferableName(&m)
 | 
			
		||||
			if name == "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if isPropertyRequired(&m) {
 | 
			
		||||
				required = append(required, name)
 | 
			
		||||
			}
 | 
			
		||||
			if err := g.generateProperty(&m); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		g.Do("},\n", nil)
 | 
			
		||||
		if len(required) > 0 {
 | 
			
		||||
			g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
 | 
			
		||||
		}
 | 
			
		||||
		g.Do("},\n},\n", nil)
 | 
			
		||||
		g.Do("Dependencies: []string{\n", args)
 | 
			
		||||
		// Map order is undefined, sort them or we may get a different file generated each time.
 | 
			
		||||
		keys := []string{}
 | 
			
		||||
		for k := range g.refTypes {
 | 
			
		||||
			keys = append(keys, k)
 | 
			
		||||
		}
 | 
			
		||||
		sort.Strings(keys)
 | 
			
		||||
		for _, k := range keys {
 | 
			
		||||
			v := g.refTypes[k]
 | 
			
		||||
			if t, _ := common.GetOpenAPITypeFormat(v.String()); t != "" {
 | 
			
		||||
				// This is a known type, we do not need a reference to it
 | 
			
		||||
				// Will eliminate special case of time.Time
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			g.Do("\"$.$\",", k)
 | 
			
		||||
		}
 | 
			
		||||
		g.Do("},\n},\n", nil)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateDescription(CommentLines []string) {
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	delPrevChar := func() {
 | 
			
		||||
		if buffer.Len() > 0 {
 | 
			
		||||
			buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, line := range CommentLines {
 | 
			
		||||
		// Ignore all lines after ---
 | 
			
		||||
		if line == "---" {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		line = strings.TrimRight(line, " ")
 | 
			
		||||
		leading := strings.TrimLeft(line, " ")
 | 
			
		||||
		switch {
 | 
			
		||||
		case len(line) == 0: // Keep paragraphs
 | 
			
		||||
			delPrevChar()
 | 
			
		||||
			buffer.WriteString("\n\n")
 | 
			
		||||
		case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
 | 
			
		||||
		case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
 | 
			
		||||
		default:
 | 
			
		||||
			if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
 | 
			
		||||
				delPrevChar()
 | 
			
		||||
				line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
 | 
			
		||||
			} else {
 | 
			
		||||
				line += " "
 | 
			
		||||
			}
 | 
			
		||||
			buffer.WriteString(line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	postDoc := strings.TrimRight(buffer.String(), "\n")
 | 
			
		||||
	postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
 | 
			
		||||
	postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
 | 
			
		||||
	postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
 | 
			
		||||
	postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
 | 
			
		||||
	postDoc = strings.Trim(postDoc, " ")
 | 
			
		||||
	if postDoc != "" {
 | 
			
		||||
		g.Do("Description: \"$.$\",\n", postDoc)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateProperty(m *types.Member) error {
 | 
			
		||||
	name := getReferableName(m)
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	g.Do("\"$.$\": {\n", name)
 | 
			
		||||
	g.Do("SchemaProps: spec.SchemaProps{\n", nil)
 | 
			
		||||
	g.generateDescription(m.CommentLines)
 | 
			
		||||
	jsonTags := getJsonTags(m)
 | 
			
		||||
	if len(jsonTags) > 1 && jsonTags[1] == "string" {
 | 
			
		||||
		g.generateSimpleProperty("string", "")
 | 
			
		||||
		g.Do("},\n},\n", nil)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	t := resolveAliasAndPtrType(m.Type)
 | 
			
		||||
	// If we can get a openAPI type and format for this type, we consider it to be simple property
 | 
			
		||||
	typeString, format := common.GetOpenAPITypeFormat(t.String())
 | 
			
		||||
	if typeString != "" {
 | 
			
		||||
		g.generateSimpleProperty(typeString, format)
 | 
			
		||||
		g.Do("},\n},\n", nil)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	switch t.Kind {
 | 
			
		||||
	case types.Builtin:
 | 
			
		||||
		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function.", t)
 | 
			
		||||
	case types.Map:
 | 
			
		||||
		if err := g.generateMapProperty(t); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case types.Slice, types.Array:
 | 
			
		||||
		if err := g.generateSliceProperty(t); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case types.Struct, types.Interface:
 | 
			
		||||
		g.generateReferenceProperty(t)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("cannot generate spec for type %v.", t)
 | 
			
		||||
	}
 | 
			
		||||
	g.Do("},\n},\n", nil)
 | 
			
		||||
	return g.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
 | 
			
		||||
	g.Do("Type: []string{\"$.$\"},\n", typeString)
 | 
			
		||||
	g.Do("Format: \"$.$\",\n", format)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
 | 
			
		||||
	var name string
 | 
			
		||||
	if t.Name.Package == "" {
 | 
			
		||||
		name = t.Name.Name
 | 
			
		||||
	} else {
 | 
			
		||||
		name = filepath.Base(t.Name.Package) + "." + t.Name.Name
 | 
			
		||||
	}
 | 
			
		||||
	g.refTypes[name] = t
 | 
			
		||||
	g.Do("Ref: spec.MustCreateRef(\"#/definitions/$.$\"),\n", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resolveAliasAndPtrType(t *types.Type) *types.Type {
 | 
			
		||||
	var prev *types.Type
 | 
			
		||||
	for prev != t {
 | 
			
		||||
		prev = t
 | 
			
		||||
		if t.Kind == types.Alias {
 | 
			
		||||
			t = t.Underlying
 | 
			
		||||
		}
 | 
			
		||||
		if t.Kind == types.Pointer {
 | 
			
		||||
			t = t.Elem
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
 | 
			
		||||
	keyType := resolveAliasAndPtrType(t.Key)
 | 
			
		||||
	elemType := resolveAliasAndPtrType(t.Elem)
 | 
			
		||||
 | 
			
		||||
	// According to OpenAPI examples, only map from string is supported
 | 
			
		||||
	if keyType.Name.Name != "string" {
 | 
			
		||||
		return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
 | 
			
		||||
	}
 | 
			
		||||
	g.Do("Type: []string{\"object\"},\n", nil)
 | 
			
		||||
	g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
 | 
			
		||||
	switch elemType.Kind {
 | 
			
		||||
	case types.Builtin:
 | 
			
		||||
		typeString, format := common.GetOpenAPITypeFormat(elemType.String())
 | 
			
		||||
		g.generateSimpleProperty(typeString, format)
 | 
			
		||||
	case types.Struct:
 | 
			
		||||
		g.generateReferenceProperty(t.Elem)
 | 
			
		||||
	case types.Slice, types.Array:
 | 
			
		||||
		g.generateSliceProperty(elemType)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
 | 
			
		||||
	}
 | 
			
		||||
	g.Do("},\n},\n},\n", nil)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
 | 
			
		||||
	elemType := resolveAliasAndPtrType(t.Elem)
 | 
			
		||||
	g.Do("Type: []string{\"array\"},\n", nil)
 | 
			
		||||
	g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
 | 
			
		||||
	switch elemType.Kind {
 | 
			
		||||
	case types.Builtin:
 | 
			
		||||
		typeString, format := common.GetOpenAPITypeFormat(elemType.String())
 | 
			
		||||
		g.generateSimpleProperty(typeString, format)
 | 
			
		||||
	case types.Struct:
 | 
			
		||||
		g.generateReferenceProperty(t.Elem)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
 | 
			
		||||
	}
 | 
			
		||||
	g.Do("},\n},\n},\n", nil)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										359
									
								
								cmd/libs/go2idl/openapi-gen/generators/openapi_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								cmd/libs/go2idl/openapi-gen/generators/openapi_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,359 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package generators
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/generator"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/namer"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/parser"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
 | 
			
		||||
	b := parser.New()
 | 
			
		||||
	for name, src := range files {
 | 
			
		||||
		if err := b.AddFile(filepath.Dir(name), name, []byte(src)); err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	u, err := b.FindTypes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	orderer := namer.Orderer{Namer: testNamer}
 | 
			
		||||
	o := orderer.OrderUniverse(u)
 | 
			
		||||
	return b, u, o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
	var testFiles = map[string]string{
 | 
			
		||||
		"base/foo/bar.go": code,
 | 
			
		||||
	}
 | 
			
		||||
	rawNamer := namer.NewRawNamer("o", nil)
 | 
			
		||||
	namers := namer.NameSystems{
 | 
			
		||||
		"raw": namer.NewRawNamer("", nil),
 | 
			
		||||
	}
 | 
			
		||||
	builder, universe, _ := construct(t, testFiles, rawNamer)
 | 
			
		||||
	context, err := generator.NewContext(builder, namers, "raw")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	buffer := &bytes.Buffer{}
 | 
			
		||||
	sw := generator.NewSnippetWriter(buffer, context, "$", "$")
 | 
			
		||||
	blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
 | 
			
		||||
	return newOpenAPITypeWriter(sw).generate(blahT), assert, buffer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSimple(t *testing.T) {
 | 
			
		||||
	err, assert, buffer := testOpenAPITypeWritter(t, `
 | 
			
		||||
package foo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/intstr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Blah is a test.
 | 
			
		||||
// +k8s:openapi=true
 | 
			
		||||
type Blah struct {
 | 
			
		||||
	// A simple string
 | 
			
		||||
	String string
 | 
			
		||||
	// A simple int
 | 
			
		||||
	Int int `+"`"+`json:",omitempty"`+"`"+`
 | 
			
		||||
	// An int considered string simple int
 | 
			
		||||
	IntString int `+"`"+`json:",string"`+"`"+`
 | 
			
		||||
	// A simple int64
 | 
			
		||||
	Int64 int64
 | 
			
		||||
	// A simple int32
 | 
			
		||||
	Int32 int32
 | 
			
		||||
	// A simple int16
 | 
			
		||||
	Int16 int16
 | 
			
		||||
	// A simple int8
 | 
			
		||||
	Int8 int8
 | 
			
		||||
	// A simple int
 | 
			
		||||
	Uint uint
 | 
			
		||||
	// A simple int64
 | 
			
		||||
	Uint64 uint64
 | 
			
		||||
	// A simple int32
 | 
			
		||||
	Uint32 uint32
 | 
			
		||||
	// A simple int16
 | 
			
		||||
	Uint16 uint16
 | 
			
		||||
	// A simple int8
 | 
			
		||||
	Uint8 uint8
 | 
			
		||||
	// A simple byte
 | 
			
		||||
	Byte byte
 | 
			
		||||
	// A simple boolean
 | 
			
		||||
	Bool bool
 | 
			
		||||
	// A simple float64
 | 
			
		||||
	Float64 float64
 | 
			
		||||
	// A simple float32
 | 
			
		||||
	Float32 float32
 | 
			
		||||
	// A simple time
 | 
			
		||||
	Time time.Time
 | 
			
		||||
	// a base64 encoded characters
 | 
			
		||||
	ByteArray []byte
 | 
			
		||||
	// an int or string type
 | 
			
		||||
	IntOrString intstr.IntOrString
 | 
			
		||||
}
 | 
			
		||||
		`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(`"foo.Blah": {
 | 
			
		||||
Schema: spec.Schema{
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "Blah is a test.",
 | 
			
		||||
Properties: map[string]spec.Schema{
 | 
			
		||||
"String": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple string",
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Int64": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int64",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int64",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Int32": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int32",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int32",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Int16": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int16",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int32",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Int8": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int8",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "byte",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Uint": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int32",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Uint64": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int64",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int64",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Uint32": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int32",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int64",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Uint16": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int16",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "int32",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Uint8": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple int8",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "byte",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Byte": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple byte",
 | 
			
		||||
Type: []string{"integer"},
 | 
			
		||||
Format: "byte",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Bool": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple boolean",
 | 
			
		||||
Type: []string{"boolean"},
 | 
			
		||||
Format: "",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Float64": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple float64",
 | 
			
		||||
Type: []string{"number"},
 | 
			
		||||
Format: "double",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Float32": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple float32",
 | 
			
		||||
Type: []string{"number"},
 | 
			
		||||
Format: "float",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"Time": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A simple time",
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "date-time",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"ByteArray": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "a base64 encoded characters",
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "byte",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"IntOrString": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "an int or string type",
 | 
			
		||||
Ref: spec.MustCreateRef("#/definitions/intstr.IntOrString"),
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","Time","ByteArray","IntOrString"},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
Dependencies: []string{
 | 
			
		||||
"intstr.IntOrString",},
 | 
			
		||||
},
 | 
			
		||||
`, buffer.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFailingSample1(t *testing.T) {
 | 
			
		||||
	err, assert, _ := testOpenAPITypeWritter(t, `
 | 
			
		||||
package foo
 | 
			
		||||
 | 
			
		||||
// Map sample tests openAPIGen.generateMapProperty method.
 | 
			
		||||
type Blah struct {
 | 
			
		||||
	// A sample String to String map
 | 
			
		||||
	StringToArray map[string]map[string]string
 | 
			
		||||
}
 | 
			
		||||
	`)
 | 
			
		||||
	if assert.Error(err, "An error was expected") {
 | 
			
		||||
		assert.Equal(err, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFailingSample2(t *testing.T) {
 | 
			
		||||
	err, assert, _ := testOpenAPITypeWritter(t, `
 | 
			
		||||
package foo
 | 
			
		||||
 | 
			
		||||
// Map sample tests openAPIGen.generateMapProperty method.
 | 
			
		||||
type Blah struct {
 | 
			
		||||
	// A sample String to String map
 | 
			
		||||
	StringToArray map[int]string
 | 
			
		||||
}	`)
 | 
			
		||||
	if assert.Error(err, "An error was expected") {
 | 
			
		||||
		assert.Equal(err, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPointer(t *testing.T) {
 | 
			
		||||
	err, assert, buffer := testOpenAPITypeWritter(t, `
 | 
			
		||||
package foo
 | 
			
		||||
 | 
			
		||||
// PointerSample demonstrate pointer's properties
 | 
			
		||||
type Blah struct {
 | 
			
		||||
	// A string pointer
 | 
			
		||||
	StringPointer *string
 | 
			
		||||
	// A struct pointer
 | 
			
		||||
	StructPointer *Blah
 | 
			
		||||
	// A slice pointer
 | 
			
		||||
	SlicePointer *[]string
 | 
			
		||||
	// A map pointer
 | 
			
		||||
	MapPointer *map[string]string
 | 
			
		||||
}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(`"foo.Blah": {
 | 
			
		||||
Schema: spec.Schema{
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "PointerSample demonstrate pointer's properties",
 | 
			
		||||
Properties: map[string]spec.Schema{
 | 
			
		||||
"StringPointer": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A string pointer",
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"StructPointer": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A struct pointer",
 | 
			
		||||
Ref: spec.MustCreateRef("#/definitions/foo.Blah"),
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"SlicePointer": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A slice pointer",
 | 
			
		||||
Type: []string{"array"},
 | 
			
		||||
Items: &spec.SchemaOrArray{
 | 
			
		||||
Schema: &spec.Schema{
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
"MapPointer": {
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Description: "A map pointer",
 | 
			
		||||
Type: []string{"object"},
 | 
			
		||||
AdditionalProperties: &spec.SchemaOrBool{
 | 
			
		||||
Schema: &spec.Schema{
 | 
			
		||||
SchemaProps: spec.SchemaProps{
 | 
			
		||||
Type: []string{"string"},
 | 
			
		||||
Format: "",
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
 | 
			
		||||
},
 | 
			
		||||
},
 | 
			
		||||
Dependencies: []string{
 | 
			
		||||
"foo.Blah",},
 | 
			
		||||
},
 | 
			
		||||
`, buffer.String())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								cmd/libs/go2idl/openapi-gen/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								cmd/libs/go2idl/openapi-gen/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// This package generates openAPI definition file to be used in open API spec generation on API servers. To generate
 | 
			
		||||
// definition for a specific type or package add "+k8s:openapi-gen=true" tag to the type/package comment lines. To
 | 
			
		||||
// exclude a type from a tagged package, add "+k8s:openapi-gen=false" tag to the type comment lines.
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/args"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	arguments := args.Default()
 | 
			
		||||
 | 
			
		||||
	// Override defaults.
 | 
			
		||||
	arguments.OutputFileBaseName = "openapi_generated"
 | 
			
		||||
 | 
			
		||||
	// Run it.
 | 
			
		||||
	if err := arguments.Execute(
 | 
			
		||||
		generators.NameSystems(),
 | 
			
		||||
		generators.DefaultNameSystem(),
 | 
			
		||||
		generators.Packages,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		glog.Fatalf("Error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(2).Info("Completed successfully.")
 | 
			
		||||
}
 | 
			
		||||
@@ -24,6 +24,7 @@ cmd/libs/go2idl/generator
 | 
			
		||||
cmd/libs/go2idl/go-to-protobuf
 | 
			
		||||
cmd/libs/go2idl/go-to-protobuf/protoc-gen-gogo
 | 
			
		||||
cmd/libs/go2idl/import-boss
 | 
			
		||||
cmd/libs/go2idl/openapi-gen
 | 
			
		||||
cmd/libs/go2idl/parser
 | 
			
		||||
cmd/libs/go2idl/set-gen
 | 
			
		||||
cmd/libs/go2idl/set-gen/generators
 | 
			
		||||
 
 | 
			
		||||
@@ -215,7 +215,7 @@ func InstallLogsSupport(mux Mux, container *restful.Container) {
 | 
			
		||||
	ws := new(restful.WebService)
 | 
			
		||||
	ws.Path("/logs")
 | 
			
		||||
	ws.Doc("get log files")
 | 
			
		||||
	ws.Route(ws.GET("/{logpath:*}").To(logFileHandler))
 | 
			
		||||
	ws.Route(ws.GET("/{logpath:*}").To(logFileHandler).Param(ws.PathParameter("logpath", "path to the log").DataType("string")))
 | 
			
		||||
	ws.Route(ws.GET("/").To(logFileListHandler))
 | 
			
		||||
 | 
			
		||||
	container.Add(ws)
 | 
			
		||||
 
 | 
			
		||||
@@ -522,17 +522,38 @@ func (s *GenericAPIServer) InstallSwaggerAPI() {
 | 
			
		||||
	swagger.RegisterSwaggerService(*s.getSwaggerConfig(), s.HandlerContainer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InstallOpenAPI installs the /swagger.json endpoint to allow new OpenAPI schema discovery.
 | 
			
		||||
// InstallOpenAPI installs spec endpoints for each web service.
 | 
			
		||||
func (s *GenericAPIServer) InstallOpenAPI() {
 | 
			
		||||
	openAPIConfig := openapi.Config{
 | 
			
		||||
		SwaggerConfig:   s.getSwaggerConfig(),
 | 
			
		||||
		IgnorePrefixes:  []string{"/swaggerapi"},
 | 
			
		||||
		Info:            &s.openAPIInfo,
 | 
			
		||||
		DefaultResponse: &s.openAPIDefaultResponse,
 | 
			
		||||
	// Install one spec per web service, an ideal client will have a ClientSet containing one client
 | 
			
		||||
	// per each of these specs.
 | 
			
		||||
	for _, w := range s.HandlerContainer.RegisteredWebServices() {
 | 
			
		||||
		if w.RootPath() == "/swaggerapi" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		info := s.openAPIInfo
 | 
			
		||||
		info.Title = info.Title + " " + w.RootPath()
 | 
			
		||||
		err := openapi.RegisterOpenAPIService(&openapi.Config{
 | 
			
		||||
			OpenAPIServePath: w.RootPath() + "/swagger.json",
 | 
			
		||||
			WebServices:      []*restful.WebService{w},
 | 
			
		||||
			ProtocolList:     []string{"https"},
 | 
			
		||||
			IgnorePrefixes:   []string{"/swaggerapi"},
 | 
			
		||||
			Info:             &info,
 | 
			
		||||
			DefaultResponse:  &s.openAPIDefaultResponse,
 | 
			
		||||
		}, s.HandlerContainer)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	err := openapi.RegisterOpenAPIService(&openAPIConfig, s.HandlerContainer)
 | 
			
		||||
	err := openapi.RegisterOpenAPIService(&openapi.Config{
 | 
			
		||||
		OpenAPIServePath: "/swagger.json",
 | 
			
		||||
		WebServices:      s.HandlerContainer.RegisteredWebServices(),
 | 
			
		||||
		ProtocolList:     []string{"https"},
 | 
			
		||||
		IgnorePrefixes:   []string{"/swaggerapi"},
 | 
			
		||||
		Info:             &s.openAPIInfo,
 | 
			
		||||
		DefaultResponse:  &s.openAPIDefaultResponse,
 | 
			
		||||
	}, s.HandlerContainer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.Fatalf("Failed to generate open api spec: %v", err)
 | 
			
		||||
		glog.Fatalf("Failed to register open api spec for root: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,213 +16,133 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
package openapi
 | 
			
		||||
 | 
			
		||||
// Note: Any reference to swagger in this document is to swagger 1.2 spec.
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/emicklei/go-restful"
 | 
			
		||||
	"github.com/emicklei/go-restful/swagger"
 | 
			
		||||
	"github.com/go-openapi/loads"
 | 
			
		||||
	"github.com/go-openapi/spec"
 | 
			
		||||
	"github.com/go-openapi/strfmt"
 | 
			
		||||
	"github.com/go-openapi/validate"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// By convention, the Swagger specification file is named swagger.json
 | 
			
		||||
	OpenAPIServePath = "/swagger.json"
 | 
			
		||||
	OpenAPIVersion   = "2.0"
 | 
			
		||||
	OpenAPIVersion = "2.0"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Config is set of configuration for openAPI spec generation.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	// SwaggerConfig is set of configuration for go-restful swagger spec generation. Currently
 | 
			
		||||
	// openAPI implementation depends on go-restful to generate models.
 | 
			
		||||
	SwaggerConfig *swagger.Config
 | 
			
		||||
	// Path to the spec file. by convention, it should name [.*/]*/swagger.json
 | 
			
		||||
	OpenAPIServePath string
 | 
			
		||||
	// List of web services for this API spec
 | 
			
		||||
	WebServices []*restful.WebService
 | 
			
		||||
 | 
			
		||||
	// List of supported protocols such as https, http, etc.
 | 
			
		||||
	ProtocolList []string
 | 
			
		||||
 | 
			
		||||
	// Info is general information about the API.
 | 
			
		||||
	Info *spec.Info
 | 
			
		||||
	// DefaultResponse will be used if an operation does not have any responses listed. It
 | 
			
		||||
	// will show up as ... "responses" : {"default" : $DefaultResponse} in swagger spec.
 | 
			
		||||
	// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
 | 
			
		||||
	DefaultResponse *spec.Response
 | 
			
		||||
	// List of webservice's path prefixes to ignore
 | 
			
		||||
	IgnorePrefixes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// +k8s:openapi-gen=target
 | 
			
		||||
type openAPI struct {
 | 
			
		||||
	config       *Config
 | 
			
		||||
	swagger      *spec.Swagger
 | 
			
		||||
	protocolList []string
 | 
			
		||||
	config             *Config
 | 
			
		||||
	swagger            *spec.Swagger
 | 
			
		||||
	protocolList       []string
 | 
			
		||||
	openAPIDefinitions *common.OpenAPIDefinitions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
 | 
			
		||||
func RegisterOpenAPIService(config *Config, containers *restful.Container) (err error) {
 | 
			
		||||
	var _ = loads.Spec
 | 
			
		||||
	var _ = strfmt.ParseDuration
 | 
			
		||||
	var _ = validate.FormatOf
 | 
			
		||||
	o := openAPI{
 | 
			
		||||
		config: config,
 | 
			
		||||
		swagger: &spec.Swagger{
 | 
			
		||||
			SwaggerProps: spec.SwaggerProps{
 | 
			
		||||
				Swagger:     OpenAPIVersion,
 | 
			
		||||
				Definitions: spec.Definitions{},
 | 
			
		||||
				Paths:       &spec.Paths{Paths: map[string]spec.PathItem{}},
 | 
			
		||||
				Info:        config.Info,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	err = o.buildSwaggerSpec()
 | 
			
		||||
 | 
			
		||||
	err = o.init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	containers.ServeMux.HandleFunc(OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 | 
			
		||||
	containers.ServeMux.HandleFunc(config.OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		resp := restful.NewResponse(w)
 | 
			
		||||
		if r.URL.Path != OpenAPIServePath {
 | 
			
		||||
		if r.URL.Path != config.OpenAPIServePath {
 | 
			
		||||
			resp.WriteErrorString(http.StatusNotFound, "Path not found!")
 | 
			
		||||
		}
 | 
			
		||||
		// TODO: we can cache json string and return it here.
 | 
			
		||||
		resp.WriteAsJson(o.swagger)
 | 
			
		||||
	})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *openAPI) buildSwaggerSpec() (err error) {
 | 
			
		||||
	if o.swagger != nil {
 | 
			
		||||
		return fmt.Errorf("Swagger spec is already built. Duplicate call to buildSwaggerSpec is not allowed.")
 | 
			
		||||
func (o *openAPI) init() error {
 | 
			
		||||
	if o.openAPIDefinitions == nil {
 | 
			
		||||
		// Compilation error here means the code generator need to run first.
 | 
			
		||||
		o.openAPIDefinitions = o.OpenAPIDefinitions()
 | 
			
		||||
	}
 | 
			
		||||
	o.protocolList, err = o.buildProtocolList()
 | 
			
		||||
	err := o.buildPaths()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	definitions, err := o.buildDefinitions()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	// no need to the keep type list in memory
 | 
			
		||||
	o.openAPIDefinitions = nil
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *openAPI) buildDefinitionRecursively(name string) error {
 | 
			
		||||
	if _, ok := o.swagger.Definitions[name]; ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	paths, err := o.buildPaths()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.swagger = &spec.Swagger{
 | 
			
		||||
		SwaggerProps: spec.SwaggerProps{
 | 
			
		||||
			Swagger:     OpenAPIVersion,
 | 
			
		||||
			Definitions: definitions,
 | 
			
		||||
			Paths:       &paths,
 | 
			
		||||
			Info:        o.config.Info,
 | 
			
		||||
		},
 | 
			
		||||
	if item, ok := (*o.openAPIDefinitions)[name]; ok {
 | 
			
		||||
		o.swagger.Definitions[name] = item.Schema
 | 
			
		||||
		for _, v := range item.Dependencies {
 | 
			
		||||
			if err := o.buildDefinitionRecursively(v); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again.", name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildDefinitions construct OpenAPI definitions using go-restful's swagger 1.2 generated models.
 | 
			
		||||
func (o *openAPI) buildDefinitions() (definitions spec.Definitions, err error) {
 | 
			
		||||
	definitions = spec.Definitions{}
 | 
			
		||||
	for _, decl := range swagger.NewSwaggerBuilder(*o.config.SwaggerConfig).ProduceAllDeclarations() {
 | 
			
		||||
		for _, swaggerModel := range decl.Models.List {
 | 
			
		||||
			_, ok := definitions[swaggerModel.Name]
 | 
			
		||||
			if ok {
 | 
			
		||||
				// TODO(mbohlool): decide what to do with repeated models
 | 
			
		||||
				// The best way is to make sure they have the same content and
 | 
			
		||||
				// fail otherwise.
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			definitions[swaggerModel.Name], err = buildModel(swaggerModel.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return definitions, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
// buildDefinitionForType build a definition for a given type and return a referable name to it's definition.
 | 
			
		||||
// This is the main function that keep track of definitions used in this spec and is depend on code generated
 | 
			
		||||
// by k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen.
 | 
			
		||||
func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
 | 
			
		||||
	t := reflect.TypeOf(sample)
 | 
			
		||||
	if t.Kind() == reflect.Ptr {
 | 
			
		||||
		t = t.Elem()
 | 
			
		||||
	}
 | 
			
		||||
	return definitions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildModel(swaggerModel swagger.Model) (ret spec.Schema, err error) {
 | 
			
		||||
	ret = spec.Schema{
 | 
			
		||||
		// SchemaProps.SubTypes is not used in go-restful, ignoring.
 | 
			
		||||
		SchemaProps: spec.SchemaProps{
 | 
			
		||||
			Description: swaggerModel.Description,
 | 
			
		||||
			Required:    swaggerModel.Required,
 | 
			
		||||
			Properties:  make(map[string]spec.Schema),
 | 
			
		||||
		},
 | 
			
		||||
		SwaggerSchemaProps: spec.SwaggerSchemaProps{
 | 
			
		||||
			Discriminator: swaggerModel.Discriminator,
 | 
			
		||||
		},
 | 
			
		||||
	name := t.String()
 | 
			
		||||
	if err := o.buildDefinitionRecursively(name); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	for _, swaggerProp := range swaggerModel.Properties.List {
 | 
			
		||||
		if _, ok := ret.Properties[swaggerProp.Name]; ok {
 | 
			
		||||
			return ret, fmt.Errorf("Duplicate property in swagger 1.2 spec: %v", swaggerProp.Name)
 | 
			
		||||
		}
 | 
			
		||||
		ret.Properties[swaggerProp.Name], err = buildProperty(swaggerProp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ret, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildProperty converts a swagger 1.2 property to an open API property.
 | 
			
		||||
func buildProperty(swaggerProperty swagger.NamedModelProperty) (openAPIProperty spec.Schema, err error) {
 | 
			
		||||
	if swaggerProperty.Property.Ref != nil {
 | 
			
		||||
		return spec.Schema{
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Ref),
 | 
			
		||||
			},
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
	openAPIProperty = spec.Schema{
 | 
			
		||||
		SchemaProps: spec.SchemaProps{
 | 
			
		||||
			Description: swaggerProperty.Property.Description,
 | 
			
		||||
			Default:     getDefaultValue(swaggerProperty.Property.DefaultValue),
 | 
			
		||||
			Enum:        make([]interface{}, len(swaggerProperty.Property.Enum)),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range swaggerProperty.Property.Enum {
 | 
			
		||||
		openAPIProperty.Enum[i] = e
 | 
			
		||||
	}
 | 
			
		||||
	openAPIProperty.Minimum, err = getFloat64OrNil(swaggerProperty.Property.Minimum)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return spec.Schema{}, err
 | 
			
		||||
	}
 | 
			
		||||
	openAPIProperty.Maximum, err = getFloat64OrNil(swaggerProperty.Property.Maximum)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return spec.Schema{}, err
 | 
			
		||||
	}
 | 
			
		||||
	if swaggerProperty.Property.UniqueItems != nil {
 | 
			
		||||
		openAPIProperty.UniqueItems = *swaggerProperty.Property.UniqueItems
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if swaggerProperty.Property.Items != nil {
 | 
			
		||||
		if swaggerProperty.Property.Items.Ref != nil {
 | 
			
		||||
			openAPIProperty.Items = &spec.SchemaOrArray{
 | 
			
		||||
				Schema: &spec.Schema{
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Items.Ref),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			openAPIProperty.Items = &spec.SchemaOrArray{
 | 
			
		||||
				Schema: &spec.Schema{},
 | 
			
		||||
			}
 | 
			
		||||
			openAPIProperty.Items.Schema.Type, openAPIProperty.Items.Schema.Format, err =
 | 
			
		||||
				buildType(swaggerProperty.Property.Items.Type, swaggerProperty.Property.Items.Format)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return spec.Schema{}, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	openAPIProperty.Type, openAPIProperty.Format, err =
 | 
			
		||||
		buildType(swaggerProperty.Property.Type, swaggerProperty.Property.Format)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return spec.Schema{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return openAPIProperty, nil
 | 
			
		||||
	return "#/definitions/" + name, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildPaths builds OpenAPI paths using go-restful's web services.
 | 
			
		||||
func (o *openAPI) buildPaths() (spec.Paths, error) {
 | 
			
		||||
	paths := spec.Paths{
 | 
			
		||||
		Paths: make(map[string]spec.PathItem),
 | 
			
		||||
	}
 | 
			
		||||
func (o *openAPI) buildPaths() error {
 | 
			
		||||
	pathsToIgnore := createTrie(o.config.IgnorePrefixes)
 | 
			
		||||
	duplicateOpId := make(map[string]bool)
 | 
			
		||||
	// Find duplicate operation IDs.
 | 
			
		||||
	for _, service := range o.config.SwaggerConfig.WebServices {
 | 
			
		||||
	for _, service := range o.config.WebServices {
 | 
			
		||||
		if pathsToIgnore.HasPrefix(service.RootPath()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -231,28 +151,32 @@ func (o *openAPI) buildPaths() (spec.Paths, error) {
 | 
			
		||||
			duplicateOpId[route.Operation] = exists
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, w := range o.config.SwaggerConfig.WebServices {
 | 
			
		||||
	for _, w := range o.config.WebServices {
 | 
			
		||||
		rootPath := w.RootPath()
 | 
			
		||||
		if pathsToIgnore.HasPrefix(rootPath) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		commonParams, err := buildParameters(w.PathParameters())
 | 
			
		||||
		commonParams, err := o.buildParameters(w.PathParameters())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return paths, err
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for path, routes := range groupRoutesByPath(w.Routes()) {
 | 
			
		||||
			// go-swagger has special variable difinition {$NAME:*} that can only be
 | 
			
		||||
			// go-swagger has special variable definition {$NAME:*} that can only be
 | 
			
		||||
			// used at the end of the path and it is not recognized by OpenAPI.
 | 
			
		||||
			if strings.HasSuffix(path, ":*}") {
 | 
			
		||||
				path = path[:len(path)-3] + "}"
 | 
			
		||||
			}
 | 
			
		||||
			inPathCommonParamsMap, err := findCommonParameters(routes)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return paths, err
 | 
			
		||||
			if pathsToIgnore.HasPrefix(path) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pathItem, exists := paths.Paths[path]
 | 
			
		||||
			// Aggregating common parameters make API spec (and generated clients) simpler
 | 
			
		||||
			inPathCommonParamsMap, err := o.findCommonParameters(routes)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			pathItem, exists := o.swagger.Paths.Paths[path]
 | 
			
		||||
			if exists {
 | 
			
		||||
				return paths, fmt.Errorf("Duplicate webservice route has been found for path: %v", path)
 | 
			
		||||
				return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
 | 
			
		||||
			}
 | 
			
		||||
			pathItem = spec.PathItem{
 | 
			
		||||
				PathItemProps: spec.PathItemProps{
 | 
			
		||||
@@ -260,16 +184,14 @@ func (o *openAPI) buildPaths() (spec.Paths, error) {
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			// add web services's parameters as well as any parameters appears in all ops, as common parameters
 | 
			
		||||
			for _, p := range commonParams {
 | 
			
		||||
				pathItem.Parameters = append(pathItem.Parameters, p)
 | 
			
		||||
			}
 | 
			
		||||
			pathItem.Parameters = append(pathItem.Parameters, commonParams...)
 | 
			
		||||
			for _, p := range inPathCommonParamsMap {
 | 
			
		||||
				pathItem.Parameters = append(pathItem.Parameters, p)
 | 
			
		||||
			}
 | 
			
		||||
			for _, route := range routes {
 | 
			
		||||
				op, err := o.buildOperations(route, inPathCommonParamsMap)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return paths, err
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				if duplicateOpId[op.ID] {
 | 
			
		||||
					// Repeated Operation IDs are not allowed in OpenAPI spec but if
 | 
			
		||||
@@ -294,36 +216,21 @@ func (o *openAPI) buildPaths() (spec.Paths, error) {
 | 
			
		||||
					pathItem.Patch = op
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			paths.Paths[path] = pathItem
 | 
			
		||||
			o.swagger.Paths.Paths[path] = pathItem
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return paths, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildProtocolList returns list of accepted protocols for this web service. If web service url has no protocol, it
 | 
			
		||||
// will default to http.
 | 
			
		||||
func (o *openAPI) buildProtocolList() ([]string, error) {
 | 
			
		||||
	uri, err := url.Parse(o.config.SwaggerConfig.WebServicesUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, err
 | 
			
		||||
	}
 | 
			
		||||
	if uri.Scheme != "" {
 | 
			
		||||
		return []string{uri.Scheme}, nil
 | 
			
		||||
	} else {
 | 
			
		||||
		return []string{"http"}, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildOperations builds operations for each webservice path
 | 
			
		||||
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (*spec.Operation, error) {
 | 
			
		||||
	ret := &spec.Operation{
 | 
			
		||||
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
 | 
			
		||||
	ret = &spec.Operation{
 | 
			
		||||
		OperationProps: spec.OperationProps{
 | 
			
		||||
			Description: route.Doc,
 | 
			
		||||
			Consumes:    route.Consumes,
 | 
			
		||||
			Produces:    route.Produces,
 | 
			
		||||
			ID:          route.Operation,
 | 
			
		||||
			Schemes:     o.protocolList,
 | 
			
		||||
			Schemes:     o.config.ProtocolList,
 | 
			
		||||
			Responses: &spec.Responses{
 | 
			
		||||
				ResponsesProps: spec.ResponsesProps{
 | 
			
		||||
					StatusCodeResponses: make(map[int]spec.Response),
 | 
			
		||||
@@ -331,26 +238,37 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Build responses
 | 
			
		||||
	for _, resp := range route.ResponseErrors {
 | 
			
		||||
		ret.Responses.StatusCodeResponses[resp.Code] = spec.Response{
 | 
			
		||||
			ResponseProps: spec.ResponseProps{
 | 
			
		||||
				Description: resp.Message,
 | 
			
		||||
				Schema: &spec.Schema{
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Ref: spec.MustCreateRef("#/definitions/" + reflect.TypeOf(resp.Model).String()),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ret, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// If there is no response but a write sample, assume that write sample is an http.StatusOK response.
 | 
			
		||||
	if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
 | 
			
		||||
		ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ret, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// If there is still no response, use default response provided.
 | 
			
		||||
	if len(ret.Responses.StatusCodeResponses) == 0 {
 | 
			
		||||
		ret.Responses.Default = o.config.DefaultResponse
 | 
			
		||||
	}
 | 
			
		||||
	// If there is a read sample, there will be a body param referring to it.
 | 
			
		||||
	if route.ReadSample != nil {
 | 
			
		||||
		if _, err := o.toSchema(reflect.TypeOf(route.ReadSample).String(), route.ReadSample); err != nil {
 | 
			
		||||
			return ret, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Build non-common Parameters
 | 
			
		||||
	ret.Parameters = make([]spec.Parameter, 0)
 | 
			
		||||
	for _, param := range route.ParameterDocs {
 | 
			
		||||
		_, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]
 | 
			
		||||
		if !isCommon {
 | 
			
		||||
			openAPIParam, err := buildParameter(param.Data())
 | 
			
		||||
		if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
 | 
			
		||||
			openAPIParam, err := o.buildParameter(param.Data())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return ret, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -360,6 +278,20 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
 | 
			
		||||
	typeName := reflect.TypeOf(model).String()
 | 
			
		||||
	schema, err := o.toSchema(typeName, model)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return spec.Response{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return spec.Response{
 | 
			
		||||
		ResponseProps: spec.ResponseProps{
 | 
			
		||||
			Description: description,
 | 
			
		||||
			Schema:      schema,
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func groupRoutesByPath(routes []restful.Route) (ret map[string][]restful.Route) {
 | 
			
		||||
	ret = make(map[string][]restful.Route)
 | 
			
		||||
	for _, r := range routes {
 | 
			
		||||
@@ -382,7 +314,7 @@ func mapKeyFromParam(param *restful.Parameter) interface{} {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
 | 
			
		||||
func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
 | 
			
		||||
	commonParamsMap := make(map[interface{}]spec.Parameter, 0)
 | 
			
		||||
	paramOpsCountByName := make(map[interface{}]int, 0)
 | 
			
		||||
	paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
 | 
			
		||||
@@ -395,7 +327,7 @@ func findCommonParameters(routes []restful.Route) (map[interface{}]spec.Paramete
 | 
			
		||||
			key := mapKeyFromParam(param)
 | 
			
		||||
			if routeParamDuplicateMap[key] {
 | 
			
		||||
				msg, _ := json.Marshal(route.ParameterDocs)
 | 
			
		||||
				return commonParamsMap, fmt.Errorf("Duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
 | 
			
		||||
				return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
 | 
			
		||||
			}
 | 
			
		||||
			routeParamDuplicateMap[key] = true
 | 
			
		||||
			paramOpsCountByName[key]++
 | 
			
		||||
@@ -404,7 +336,7 @@ func findCommonParameters(routes []restful.Route) (map[interface{}]spec.Paramete
 | 
			
		||||
	}
 | 
			
		||||
	for key, count := range paramOpsCountByName {
 | 
			
		||||
		if count == len(routes) {
 | 
			
		||||
			openAPIParam, err := buildParameter(paramNameKindToDataMap[key])
 | 
			
		||||
			openAPIParam, err := o.buildParameter(paramNameKindToDataMap[key])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return commonParamsMap, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -414,7 +346,31 @@ func findCommonParameters(routes []restful.Route) (map[interface{}]spec.Paramete
 | 
			
		||||
	return commonParamsMap, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
 | 
			
		||||
func (o *openAPI) toSchema(typeName string, model interface{}) (_ *spec.Schema, err error) {
 | 
			
		||||
	if openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(typeName); openAPIType != "" {
 | 
			
		||||
		return &spec.Schema{
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Type:   []string{openAPIType},
 | 
			
		||||
				Format: openAPIFormat,
 | 
			
		||||
			},
 | 
			
		||||
		}, nil
 | 
			
		||||
	} else {
 | 
			
		||||
		ref := "#/definitions/" + typeName
 | 
			
		||||
		if model != nil {
 | 
			
		||||
			ref, err = o.buildDefinitionForType(model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return &spec.Schema{
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Ref: spec.MustCreateRef(ref),
 | 
			
		||||
			},
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
 | 
			
		||||
	ret = spec.Parameter{
 | 
			
		||||
		ParamProps: spec.ParamProps{
 | 
			
		||||
			Name:        restParam.Name,
 | 
			
		||||
@@ -425,16 +381,12 @@ func buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err er
 | 
			
		||||
	switch restParam.Kind {
 | 
			
		||||
	case restful.BodyParameterKind:
 | 
			
		||||
		ret.In = "body"
 | 
			
		||||
		ret.Schema = &spec.Schema{
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Ref: spec.MustCreateRef("#/definitions/" + restParam.DataType),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		return ret, nil
 | 
			
		||||
		ret.Schema, err = o.toSchema(restParam.DataType, nil)
 | 
			
		||||
		return ret, err
 | 
			
		||||
	case restful.PathParameterKind:
 | 
			
		||||
		ret.In = "path"
 | 
			
		||||
		if !restParam.Required {
 | 
			
		||||
			return ret, fmt.Errorf("Path parameters should be marked at required for parameter %v", restParam)
 | 
			
		||||
			return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
 | 
			
		||||
		}
 | 
			
		||||
	case restful.QueryParameterKind:
 | 
			
		||||
		ret.In = "query"
 | 
			
		||||
@@ -443,26 +395,22 @@ func buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err er
 | 
			
		||||
	case restful.FormParameterKind:
 | 
			
		||||
		ret.In = "form"
 | 
			
		||||
	default:
 | 
			
		||||
		return ret, fmt.Errorf("Unknown restful operation kind : %v", restParam.Kind)
 | 
			
		||||
		return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
 | 
			
		||||
	}
 | 
			
		||||
	if !isSimpleDataType(restParam.DataType) {
 | 
			
		||||
		return ret, fmt.Errorf("Restful DataType should be a simple type, but got : %v", restParam.DataType)
 | 
			
		||||
	openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(restParam.DataType)
 | 
			
		||||
	if openAPIType == "" {
 | 
			
		||||
		return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
 | 
			
		||||
	}
 | 
			
		||||
	ret.Type = restParam.DataType
 | 
			
		||||
	ret.Format = restParam.DataFormat
 | 
			
		||||
	ret.Type = openAPIType
 | 
			
		||||
	ret.Format = openAPIFormat
 | 
			
		||||
	ret.UniqueItems = !restParam.AllowMultiple
 | 
			
		||||
	// TODO(mbohlool): make sure the type of default value matches Type
 | 
			
		||||
	if restParam.DefaultValue != "" {
 | 
			
		||||
		ret.Default = restParam.DefaultValue
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
 | 
			
		||||
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
 | 
			
		||||
	ret = make([]spec.Parameter, len(restParam))
 | 
			
		||||
	for i, v := range restParam {
 | 
			
		||||
		ret[i], err = buildParameter(v.Data())
 | 
			
		||||
		ret[i], err = o.buildParameter(v.Data())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ret, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -470,60 +418,16 @@ func buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isSimpleDataType(typeName string) bool {
 | 
			
		||||
	switch typeName {
 | 
			
		||||
	// Note that "file" intentionally kept out of this list as it is not being used.
 | 
			
		||||
	// "file" type has more requirements.
 | 
			
		||||
	case "string", "number", "integer", "boolean", "array":
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getFloat64OrNil(str string) (*float64, error) {
 | 
			
		||||
	if len(str) > 0 {
 | 
			
		||||
		num, err := strconv.ParseFloat(str, 64)
 | 
			
		||||
		return &num, err
 | 
			
		||||
	}
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(mbohlool): Convert default value type to the type of parameter
 | 
			
		||||
func getDefaultValue(str swagger.Special) interface{} {
 | 
			
		||||
	if len(str) > 0 {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildType(swaggerType *string, swaggerFormat string) ([]string, string, error) {
 | 
			
		||||
	if swaggerType == nil {
 | 
			
		||||
		return []string{}, "", nil
 | 
			
		||||
	}
 | 
			
		||||
	switch *swaggerType {
 | 
			
		||||
	case "integer", "number", "string", "boolean", "array", "object", "file":
 | 
			
		||||
		return []string{*swaggerType}, swaggerFormat, nil
 | 
			
		||||
	case "int":
 | 
			
		||||
		return []string{"integer"}, "int32", nil
 | 
			
		||||
	case "long":
 | 
			
		||||
		return []string{"integer"}, "int64", nil
 | 
			
		||||
	case "float", "double":
 | 
			
		||||
		return []string{"number"}, *swaggerType, nil
 | 
			
		||||
	case "byte", "date", "datetime", "date-time":
 | 
			
		||||
		return []string{"string"}, *swaggerType, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return []string{}, "", fmt.Errorf("Unrecognized swagger 1.2 type : %v, %v", swaggerType, swaggerFormat)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A simple trie implementation with Add an HasPrefix methods only.
 | 
			
		||||
type trie struct {
 | 
			
		||||
	children map[byte]*trie
 | 
			
		||||
	wordTail bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createTrie(list []string) trie {
 | 
			
		||||
	ret := trie{
 | 
			
		||||
		children: make(map[byte]*trie),
 | 
			
		||||
		wordTail: false,
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range list {
 | 
			
		||||
		ret.Add(v)
 | 
			
		||||
@@ -536,22 +440,31 @@ func (t *trie) Add(v string) {
 | 
			
		||||
	for _, b := range []byte(v) {
 | 
			
		||||
		child, exists := root.children[b]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			child = new(trie)
 | 
			
		||||
			child.children = make(map[byte]*trie)
 | 
			
		||||
			child = &trie{
 | 
			
		||||
				children: make(map[byte]*trie),
 | 
			
		||||
				wordTail: false,
 | 
			
		||||
			}
 | 
			
		||||
			root.children[b] = child
 | 
			
		||||
		}
 | 
			
		||||
		root = child
 | 
			
		||||
	}
 | 
			
		||||
	root.wordTail = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *trie) HasPrefix(v string) bool {
 | 
			
		||||
	root := t
 | 
			
		||||
	if root.wordTail {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, b := range []byte(v) {
 | 
			
		||||
		child, exists := root.children[b]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if child.wordTail {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		root = child
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,56 +22,119 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/emicklei/go-restful"
 | 
			
		||||
	"github.com/emicklei/go-restful/swagger"
 | 
			
		||||
	"github.com/go-openapi/spec"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
 | 
			
		||||
	"sort"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// setUp is a convenience function for setting up for (most) tests.
 | 
			
		||||
func setUp(t *testing.T, fullMethods bool) (openAPI, *assert.Assertions) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
	config := Config{
 | 
			
		||||
		SwaggerConfig: getSwaggerConfig(fullMethods),
 | 
			
		||||
		Info: &spec.Info{
 | 
			
		||||
			InfoProps: spec.InfoProps{
 | 
			
		||||
				Title:       "TestAPI",
 | 
			
		||||
				Description: "Test API",
 | 
			
		||||
	config := getConfig(fullMethods)
 | 
			
		||||
	return openAPI{
 | 
			
		||||
		config: config,
 | 
			
		||||
		swagger: &spec.Swagger{
 | 
			
		||||
			SwaggerProps: spec.SwaggerProps{
 | 
			
		||||
				Swagger:     OpenAPIVersion,
 | 
			
		||||
				Definitions: spec.Definitions{},
 | 
			
		||||
				Paths:       &spec.Paths{Paths: map[string]spec.PathItem{}},
 | 
			
		||||
				Info:        config.Info,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return openAPI{config: &config}, assert
 | 
			
		||||
		openAPIDefinitions: &common.OpenAPIDefinitions{
 | 
			
		||||
			"openapi.TestInput":  *TestInput{}.OpenAPIDefinition(),
 | 
			
		||||
			"openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
 | 
			
		||||
		},
 | 
			
		||||
	}, assert
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func noOp(request *restful.Request, response *restful.Response) {}
 | 
			
		||||
 | 
			
		||||
// Test input
 | 
			
		||||
type TestInput struct {
 | 
			
		||||
	Name string   `json:"name,omitempty"`
 | 
			
		||||
	// Name of the input
 | 
			
		||||
	Name string `json:"name,omitempty"`
 | 
			
		||||
	// ID of the input
 | 
			
		||||
	ID   int      `json:"id,omitempty"`
 | 
			
		||||
	Tags []string `json:"tags,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test output
 | 
			
		||||
type TestOutput struct {
 | 
			
		||||
	Name  string `json:"name,omitempty"`
 | 
			
		||||
	Count int    `json:"count,omitempty"`
 | 
			
		||||
	// Name of the output
 | 
			
		||||
	Name string `json:"name,omitempty"`
 | 
			
		||||
	// Number of outputs
 | 
			
		||||
	Count int `json:"count,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TestInput) SwaggerDoc() map[string]string {
 | 
			
		||||
	return map[string]string{
 | 
			
		||||
		"":     "Test input",
 | 
			
		||||
		"name": "Name of the input",
 | 
			
		||||
		"id":   "ID of the input",
 | 
			
		||||
func (_ TestInput) OpenAPIDefinition() *common.OpenAPIDefinition {
 | 
			
		||||
	schema := spec.Schema{}
 | 
			
		||||
	schema.Description = "Test input"
 | 
			
		||||
	schema.Properties = map[string]spec.Schema{
 | 
			
		||||
		"name": {
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Description: "Name of the input",
 | 
			
		||||
				Type:        []string{"string"},
 | 
			
		||||
				Format:      "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"id": {
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Description: "ID of the input",
 | 
			
		||||
				Type:        []string{"integer"},
 | 
			
		||||
				Format:      "int32",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"tags": {
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Description: "",
 | 
			
		||||
				Type:        []string{"array"},
 | 
			
		||||
				Items: &spec.SchemaOrArray{
 | 
			
		||||
					Schema: &spec.Schema{
 | 
			
		||||
						SchemaProps: spec.SchemaProps{
 | 
			
		||||
							Type:   []string{"string"},
 | 
			
		||||
							Format: "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return &common.OpenAPIDefinition{
 | 
			
		||||
		Schema:       schema,
 | 
			
		||||
		Dependencies: []string{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TestOutput) SwaggerDoc() map[string]string {
 | 
			
		||||
	return map[string]string{
 | 
			
		||||
		"":      "Test output",
 | 
			
		||||
		"name":  "Name of the output",
 | 
			
		||||
		"count": "Number of outputs",
 | 
			
		||||
func (_ TestOutput) OpenAPIDefinition() *common.OpenAPIDefinition {
 | 
			
		||||
	schema := spec.Schema{}
 | 
			
		||||
	schema.Description = "Test output"
 | 
			
		||||
	schema.Properties = map[string]spec.Schema{
 | 
			
		||||
		"name": {
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Description: "Name of the output",
 | 
			
		||||
				Type:        []string{"string"},
 | 
			
		||||
				Format:      "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"count": {
 | 
			
		||||
			SchemaProps: spec.SchemaProps{
 | 
			
		||||
				Description: "Number of outputs",
 | 
			
		||||
				Type:        []string{"integer"},
 | 
			
		||||
				Format:      "int32",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return &common.OpenAPIDefinition{
 | 
			
		||||
		Schema:       schema,
 | 
			
		||||
		Dependencies: []string{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ common.OpenAPIDefinitionGetter = TestInput{}
 | 
			
		||||
var _ common.OpenAPIDefinitionGetter = TestOutput{}
 | 
			
		||||
 | 
			
		||||
func getTestRoute(ws *restful.WebService, method string, additionalParams bool) *restful.RouteBuilder {
 | 
			
		||||
	ret := ws.Method(method).
 | 
			
		||||
		Path("/test/{path:*}").
 | 
			
		||||
@@ -92,7 +155,7 @@ func getTestRoute(ws *restful.WebService, method string, additionalParams bool)
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSwaggerConfig(fullMethods bool) *swagger.Config {
 | 
			
		||||
func getConfig(fullMethods bool) *Config {
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	container := restful.NewContainer()
 | 
			
		||||
	container.ServeMux = mux
 | 
			
		||||
@@ -120,9 +183,16 @@ func getSwaggerConfig(fullMethods bool) *swagger.Config {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	container.Add(ws)
 | 
			
		||||
	return &swagger.Config{
 | 
			
		||||
		WebServicesUrl: "https://test-server",
 | 
			
		||||
		WebServices:    container.RegisteredWebServices(),
 | 
			
		||||
	return &Config{
 | 
			
		||||
		WebServices:      container.RegisteredWebServices(),
 | 
			
		||||
		ProtocolList:     []string{"https"},
 | 
			
		||||
		OpenAPIServePath: "/swagger.json",
 | 
			
		||||
		Info: &spec.Info{
 | 
			
		||||
			InfoProps: spec.InfoProps{
 | 
			
		||||
				Title:       "TestAPI",
 | 
			
		||||
				Description: "Test API",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -285,27 +355,23 @@ func getTestInputDefinition() spec.Schema {
 | 
			
		||||
	return spec.Schema{
 | 
			
		||||
		SchemaProps: spec.SchemaProps{
 | 
			
		||||
			Description: "Test input",
 | 
			
		||||
			Required:    []string{},
 | 
			
		||||
			Properties: map[string]spec.Schema{
 | 
			
		||||
				"id": {
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Description: "ID of the input",
 | 
			
		||||
						Type:        spec.StringOrArray{"integer"},
 | 
			
		||||
						Format:      "int32",
 | 
			
		||||
						Enum:        []interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"name": {
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Description: "Name of the input",
 | 
			
		||||
						Type:        spec.StringOrArray{"string"},
 | 
			
		||||
						Enum:        []interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"tags": {
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Type: spec.StringOrArray{"array"},
 | 
			
		||||
						Enum: []interface{}{},
 | 
			
		||||
						Items: &spec.SchemaOrArray{
 | 
			
		||||
							Schema: &spec.Schema{
 | 
			
		||||
								SchemaProps: spec.SchemaProps{
 | 
			
		||||
@@ -324,21 +390,18 @@ func getTestOutputDefinition() spec.Schema {
 | 
			
		||||
	return spec.Schema{
 | 
			
		||||
		SchemaProps: spec.SchemaProps{
 | 
			
		||||
			Description: "Test output",
 | 
			
		||||
			Required:    []string{},
 | 
			
		||||
			Properties: map[string]spec.Schema{
 | 
			
		||||
				"count": {
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Description: "Number of outputs",
 | 
			
		||||
						Type:        spec.StringOrArray{"integer"},
 | 
			
		||||
						Format:      "int32",
 | 
			
		||||
						Enum:        []interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"name": {
 | 
			
		||||
					SchemaProps: spec.SchemaProps{
 | 
			
		||||
						Description: "Name of the output",
 | 
			
		||||
						Type:        spec.StringOrArray{"string"},
 | 
			
		||||
						Enum:        []interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -369,49 +432,10 @@ func TestBuildSwaggerSpec(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	err := o.buildSwaggerSpec()
 | 
			
		||||
	err := o.init()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		sortParameters(expected)
 | 
			
		||||
		sortParameters(o.swagger)
 | 
			
		||||
		assert.Equal(expected, o.swagger)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuildSwaggerSpecTwice(t *testing.T) {
 | 
			
		||||
	o, assert := setUp(t, true)
 | 
			
		||||
	err := o.buildSwaggerSpec()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		assert.Error(o.buildSwaggerSpec(), "Swagger spec is already built. Duplicate call to buildSwaggerSpec is not allowed.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
func TestBuildDefinitions(t *testing.T) {
 | 
			
		||||
	o, assert := setUp(t, true)
 | 
			
		||||
	expected := spec.Definitions{
 | 
			
		||||
		"openapi.TestInput":  getTestInputDefinition(),
 | 
			
		||||
		"openapi.TestOutput": getTestOutputDefinition(),
 | 
			
		||||
	}
 | 
			
		||||
	def, err := o.buildDefinitions()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		assert.Equal(expected, def)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuildProtocolList(t *testing.T) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
	o := openAPI{config: &Config{SwaggerConfig: &swagger.Config{WebServicesUrl: "https://something"}}}
 | 
			
		||||
	p, err := o.buildProtocolList()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		assert.Equal([]string{"https"}, p)
 | 
			
		||||
	}
 | 
			
		||||
	o = openAPI{config: &Config{SwaggerConfig: &swagger.Config{WebServicesUrl: "http://something"}}}
 | 
			
		||||
	p, err = o.buildProtocolList()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		assert.Equal([]string{"http"}, p)
 | 
			
		||||
	}
 | 
			
		||||
	o = openAPI{config: &Config{SwaggerConfig: &swagger.Config{WebServicesUrl: "something"}}}
 | 
			
		||||
	p, err = o.buildProtocolList()
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		assert.Equal([]string{"http"}, p)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -342,7 +342,8 @@ func (s *Server) InstallDebuggingHandlers() {
 | 
			
		||||
		Operation("getLogs"))
 | 
			
		||||
	ws.Route(ws.GET("/{logpath:*}").
 | 
			
		||||
		To(s.getLogs).
 | 
			
		||||
		Operation("getLogs"))
 | 
			
		||||
		Operation("getLogs").
 | 
			
		||||
		Param(ws.PathParameter("logpath", "path to the log").DataType("string")))
 | 
			
		||||
	s.restfulCont.Add(ws)
 | 
			
		||||
 | 
			
		||||
	ws = new(restful.WebService)
 | 
			
		||||
 
 | 
			
		||||
@@ -821,6 +821,16 @@ func decodeResponse(resp *http.Response, obj interface{}) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeResponseToFile(resp *http.Response, filename string) error {
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	data, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return ioutil.WriteFile(filename, data, 0755)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInstallThirdPartyAPIGet(t *testing.T) {
 | 
			
		||||
	for _, version := range versionsToTest {
 | 
			
		||||
		testInstallThirdPartyAPIGetVersion(t, version)
 | 
			
		||||
@@ -1228,6 +1238,7 @@ func TestValidOpenAPISpec(t *testing.T) {
 | 
			
		||||
	defer etcdserver.Terminate(t)
 | 
			
		||||
 | 
			
		||||
	config.EnableOpenAPISupport = true
 | 
			
		||||
	config.EnableIndex = true
 | 
			
		||||
	config.OpenAPIInfo = spec.Info{
 | 
			
		||||
		InfoProps: spec.InfoProps{
 | 
			
		||||
			Title:   "Kubernetes",
 | 
			
		||||
@@ -1262,12 +1273,67 @@ func TestValidOpenAPISpec(t *testing.T) {
 | 
			
		||||
		assert.NoError(res.AsError())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO(mehdy): The actual validation part of these tests are timing out on jerkin but passing locally. Enable it after debugging timeout issue.
 | 
			
		||||
	disableValidation := true
 | 
			
		||||
 | 
			
		||||
	// Saving specs to a temporary folder is a good way to debug spec generation without bringing up an actual
 | 
			
		||||
	// api server.
 | 
			
		||||
	saveSwaggerSpecs := false
 | 
			
		||||
 | 
			
		||||
	// Validate OpenApi spec
 | 
			
		||||
	doc, err := loads.Spec(server.URL + "/swagger.json")
 | 
			
		||||
	if assert.NoError(err) {
 | 
			
		||||
		// TODO(mehdy): This test is timing out on jerkin but passing locally. Enable it after debugging timeout issue.
 | 
			
		||||
		_ = validate.NewSpecValidator(doc.Schema(), strfmt.Default)
 | 
			
		||||
		// res, _ := validator.Validate(doc)
 | 
			
		||||
		// assert.NoError(res.AsError())
 | 
			
		||||
		validator := validate.NewSpecValidator(doc.Schema(), strfmt.Default)
 | 
			
		||||
		if !disableValidation {
 | 
			
		||||
			res, warns := validator.Validate(doc)
 | 
			
		||||
			assert.NoError(res.AsError())
 | 
			
		||||
			if !warns.IsValid() {
 | 
			
		||||
				t.Logf("Open API spec on root has some warnings : %v", warns)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			t.Logf("Validation is disabled because it is timing out on jenkins put passing locally.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// validate specs on each end-point
 | 
			
		||||
	resp, err = http.Get(server.URL)
 | 
			
		||||
	if !assert.NoError(err) {
 | 
			
		||||
		t.Errorf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(http.StatusOK, resp.StatusCode)
 | 
			
		||||
	var list unversioned.RootPaths
 | 
			
		||||
	if assert.NoError(decodeResponse(resp, &list)) {
 | 
			
		||||
		for _, path := range list.Paths {
 | 
			
		||||
			if !strings.HasPrefix(path, "/api") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			t.Logf("Validating open API spec on %v ...", path)
 | 
			
		||||
 | 
			
		||||
			if saveSwaggerSpecs {
 | 
			
		||||
				resp, err = http.Get(server.URL + path + "/swagger.json")
 | 
			
		||||
				if !assert.NoError(err) {
 | 
			
		||||
					t.Errorf("unexpected error: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
				assert.Equal(http.StatusOK, resp.StatusCode)
 | 
			
		||||
				assert.NoError(writeResponseToFile(resp, "/tmp/swagger_"+strings.Replace(path, "/", "_", -1)+".json"))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Validate OpenApi spec on path
 | 
			
		||||
			doc, err := loads.Spec(server.URL + path + "/swagger.json")
 | 
			
		||||
			if assert.NoError(err) {
 | 
			
		||||
				validator := validate.NewSpecValidator(doc.Schema(), strfmt.Default)
 | 
			
		||||
				if !disableValidation {
 | 
			
		||||
					res, warns := validator.Validate(doc)
 | 
			
		||||
					assert.NoError(res.AsError())
 | 
			
		||||
					if !warns.IsValid() {
 | 
			
		||||
						t.Logf("Open API spec on %v has some warnings : %v", path, warns)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					t.Logf("Validation is disabled because it is timing out on jenkins put passing locally.")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user