Files
virtual-kubelet/vendor/github.com/go-openapi/analysis/flatten.go
Loc Nguyen 513cebe7b7 VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
2018-06-04 15:41:32 -07:00

757 lines
19 KiB
Go

package analysis
import (
"fmt"
"log"
"net/http"
"path"
"sort"
"strings"
"strconv"
"github.com/go-openapi/jsonpointer"
swspec "github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
// FlattenOpts configuration for flattening a swagger specification.
type FlattenOpts struct {
Spec *Spec
BasePath string
_ struct{} // require keys
}
// ExpandOpts creates a spec.ExpandOptions to configure expanding a specification document.
func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *swspec.ExpandOptions {
return &swspec.ExpandOptions{RelativeBase: f.BasePath, SkipSchemas: skipSchemas}
}
// Swagger gets the swagger specification for this flatten operation
func (f *FlattenOpts) Swagger() *swspec.Swagger {
return f.Spec.spec
}
// Flatten an analyzed spec.
//
// To flatten a spec means:
//
// Expand the parameters, responses, path items, parameter items and header items.
// Import external (http, file) references so they become internal to the document.
// Move every inline schema to be a definition with an auto-generated name in a depth-first fashion.
// Rewritten schemas get a vendor extension x-go-gen-location so we know in which package they need to be rendered.
func Flatten(opts FlattenOpts) error {
// recursively expand responses, parameters, path items and items
err := swspec.ExpandSpec(opts.Swagger(), opts.ExpandOpts(true))
if err != nil {
return err
}
opts.Spec.reload() // re-analyze
// at this point there are no other references left but schemas
if err := importExternalReferences(&opts); err != nil {
return err
}
opts.Spec.reload() // re-analyze
// rewrite the inline schemas (schemas that aren't simple types or arrays of simple types)
if err := nameInlinedSchemas(&opts); err != nil {
return err
}
opts.Spec.reload() // re-analyze
// TODO: simplifiy known schema patterns to flat objects with properties?
return nil
}
func nameInlinedSchemas(opts *FlattenOpts) error {
namer := &inlineSchemaNamer{Spec: opts.Swagger(), Operations: opRefsByRef(gatherOperations(opts.Spec, nil))}
depthFirst := sortDepthFirst(opts.Spec.allSchemas)
for _, key := range depthFirst {
sch := opts.Spec.allSchemas[key]
if sch.Schema != nil && sch.Schema.Ref.String() == "" && !sch.TopLevel { // inline schema
asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
if err != nil {
return fmt.Errorf("schema analysis [%s]: %v", sch.Ref.String(), err)
}
if !asch.IsSimpleSchema { // complex schemas get moved
if err := namer.Name(key, sch.Schema, asch); err != nil {
return err
}
}
}
}
return nil
}
var depthGroupOrder = []string{"sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition"}
func sortDepthFirst(data map[string]SchemaRef) (sorted []string) {
// group by category (shared params, op param, statuscode response, default response, definitions)
// sort groups internally by number of parts in the key and lexical names
// flatten groups into a single list of keys
grouped := make(map[string]keys, len(data))
for k := range data {
split := keyParts(k)
var pk string
if split.IsSharedOperationParam() {
pk = "sharedOpParam"
}
if split.IsOperationParam() {
pk = "opParam"
}
if split.IsStatusCodeResponse() {
pk = "codeResponse"
}
if split.IsDefaultResponse() {
pk = "defaultResponse"
}
if split.IsDefinition() {
pk = "definition"
}
grouped[pk] = append(grouped[pk], key{len(split), k})
}
for _, pk := range depthGroupOrder {
res := grouped[pk]
sort.Sort(res)
for _, v := range res {
sorted = append(sorted, v.Key)
}
}
return
}
type key struct {
Segments int
Key string
}
type keys []key
func (k keys) Len() int { return len(k) }
func (k keys) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k keys) Less(i, j int) bool {
return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key)
}
type inlineSchemaNamer struct {
Spec *swspec.Swagger
Operations map[string]opRef
}
func opRefsByRef(oprefs map[string]opRef) map[string]opRef {
result := make(map[string]opRef, len(oprefs))
for _, v := range oprefs {
result[v.Ref.String()] = v
}
return result
}
func (isn *inlineSchemaNamer) Name(key string, schema *swspec.Schema, aschema *AnalyzedSchema) error {
if swspec.Debug {
log.Printf("naming inlined schema at %s", key)
}
parts := keyParts(key)
for _, name := range namesFromKey(parts, aschema, isn.Operations) {
if name != "" {
// create unique name
newName := uniqifyName(isn.Spec.Definitions, swag.ToJSONName(name))
// clone schema
sch, err := cloneSchema(schema)
if err != nil {
return err
}
// replace values on schema
if err := rewriteSchemaToRef(isn.Spec, key, swspec.MustCreateRef("#/definitions/"+newName)); err != nil {
return fmt.Errorf("name inlined schema: %v", err)
}
sch.AddExtension("x-go-gen-location", genLocation(parts))
// fmt.Printf("{\n %q,\n \"\",\n spec.MustCreateRef(%q),\n \"\",\n},\n", key, "#/definitions/"+newName)
// save cloned schema to definitions
saveSchema(isn.Spec, newName, sch)
}
}
return nil
}
func genLocation(parts splitKey) string {
if parts.IsOperation() {
return "operations"
}
if parts.IsDefinition() {
return "models"
}
return ""
}
func uniqifyName(definitions swspec.Definitions, name string) string {
if name == "" {
name = "oaiGen"
}
if len(definitions) == 0 {
return name
}
if _, ok := definitions[name]; !ok {
return name
}
name += "OAIGen"
var idx int
unique := name
_, known := definitions[unique]
for known {
idx++
unique = fmt.Sprintf("%s%d", name, idx)
_, known = definitions[unique]
}
return unique
}
func namesFromKey(parts splitKey, aschema *AnalyzedSchema, operations map[string]opRef) []string {
var baseNames [][]string
var startIndex int
if parts.IsOperation() {
// params
if parts.IsOperationParam() || parts.IsSharedOperationParam() {
piref := parts.PathItemRef()
if piref.String() != "" && parts.IsOperationParam() {
if op, ok := operations[piref.String()]; ok {
startIndex = 5
baseNames = append(baseNames, []string{op.ID, "params", "body"})
}
} else if parts.IsSharedOperationParam() {
pref := parts.PathRef()
for k, v := range operations {
if strings.HasPrefix(k, pref.String()) {
startIndex = 4
baseNames = append(baseNames, []string{v.ID, "params", "body"})
}
}
}
}
// responses
if parts.IsOperationResponse() {
piref := parts.PathItemRef()
if piref.String() != "" {
if op, ok := operations[piref.String()]; ok {
startIndex = 6
baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
}
}
}
}
// definitions
if parts.IsDefinition() {
nm := parts.DefinitionName()
if nm != "" {
startIndex = 2
baseNames = append(baseNames, []string{parts.DefinitionName()})
}
}
var result []string
for _, segments := range baseNames {
nm := parts.BuildName(segments, startIndex, aschema)
if nm != "" {
result = append(result, nm)
}
}
sort.Strings(result)
return result
}
const (
pths = "paths"
responses = "responses"
parameters = "parameters"
definitions = "definitions"
)
var ignoredKeys map[string]struct{}
func init() {
ignoredKeys = map[string]struct{}{
"schema": {},
"properties": {},
"not": {},
"anyOf": {},
"oneOf": {},
}
}
type splitKey []string
func (s splitKey) IsDefinition() bool {
return len(s) > 1 && s[0] == definitions
}
func (s splitKey) DefinitionName() string {
if !s.IsDefinition() {
return ""
}
return s[1]
}
func (s splitKey) BuildName(segments []string, startIndex int, aschema *AnalyzedSchema) string {
for _, part := range s[startIndex:] {
if _, ignored := ignoredKeys[part]; !ignored {
if part == "items" || part == "additionalItems" {
if aschema.IsTuple || aschema.IsTupleWithExtra {
segments = append(segments, "tuple")
} else {
segments = append(segments, "items")
}
if part == "additionalItems" {
segments = append(segments, part)
}
continue
}
segments = append(segments, part)
}
}
return strings.Join(segments, " ")
}
func (s splitKey) IsOperation() bool {
return len(s) > 1 && s[0] == pths
}
func (s splitKey) IsSharedOperationParam() bool {
return len(s) > 2 && s[0] == pths && s[2] == parameters
}
func (s splitKey) IsOperationParam() bool {
return len(s) > 3 && s[0] == pths && s[3] == parameters
}
func (s splitKey) IsOperationResponse() bool {
return len(s) > 3 && s[0] == pths && s[3] == responses
}
func (s splitKey) IsDefaultResponse() bool {
return len(s) > 4 && s[0] == pths && s[3] == responses && s[4] == "default"
}
func (s splitKey) IsStatusCodeResponse() bool {
isInt := func() bool {
_, err := strconv.Atoi(s[4])
return err == nil
}
return len(s) > 4 && s[0] == pths && s[3] == responses && isInt()
}
func (s splitKey) ResponseName() string {
if s.IsStatusCodeResponse() {
code, _ := strconv.Atoi(s[4])
return http.StatusText(code)
}
if s.IsDefaultResponse() {
return "Default"
}
return ""
}
var validMethods map[string]struct{}
func init() {
validMethods = map[string]struct{}{
"GET": {},
"HEAD": {},
"OPTIONS": {},
"PATCH": {},
"POST": {},
"PUT": {},
"DELETE": {},
}
}
func (s splitKey) PathItemRef() swspec.Ref {
if len(s) < 3 {
return swspec.Ref{}
}
pth, method := s[1], s[2]
if _, validMethod := validMethods[strings.ToUpper(method)]; !validMethod && !strings.HasPrefix(method, "x-") {
return swspec.Ref{}
}
return swspec.MustCreateRef("#" + path.Join("/", pths, jsonpointer.Escape(pth), strings.ToUpper(method)))
}
func (s splitKey) PathRef() swspec.Ref {
if !s.IsOperation() {
return swspec.Ref{}
}
return swspec.MustCreateRef("#" + path.Join("/", pths, jsonpointer.Escape(s[1])))
}
func keyParts(key string) splitKey {
var res []string
for _, part := range strings.Split(key[1:], "/") {
if part != "" {
res = append(res, jsonpointer.Unescape(part))
}
}
return res
}
func rewriteSchemaToRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
if swspec.Debug {
log.Printf("rewriting schema to ref for %s with %s", key, ref.String())
}
pth := key[1:]
ptr, err := jsonpointer.New(pth)
if err != nil {
return err
}
value, _, err := ptr.Get(spec)
if err != nil {
return err
}
switch refable := value.(type) {
case *swspec.Schema:
return rewriteParentRef(spec, key, ref)
case *swspec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
case *swspec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
case swspec.Schema:
return rewriteParentRef(spec, key, ref)
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
func rewriteParentRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
pth := key[1:]
parent, entry := path.Dir(pth), path.Base(pth)
if swspec.Debug {
log.Println("getting schema holder at:", parent)
}
pptr, err := jsonpointer.New(parent)
if err != nil {
return err
}
pvalue, _, err := pptr.Get(spec)
if err != nil {
return fmt.Errorf("can't get parent for %s: %v", parent, err)
}
if swspec.Debug {
log.Printf("rewriting holder for %T", pvalue)
}
switch container := pvalue.(type) {
case swspec.Response:
if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
return err
}
case *swspec.Response:
container.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.Responses:
statusCode, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
resp := container.StatusCodeResponses[statusCode]
resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container.StatusCodeResponses[statusCode] = resp
case map[string]swspec.Response:
resp := container[entry]
resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[entry] = resp
case swspec.Parameter:
if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
return err
}
case map[string]swspec.Parameter:
param := container[entry]
param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[entry] = param
case []swspec.Parameter:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
param := container[idx]
param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[idx] = param
case swspec.Definitions:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case map[string]swspec.Schema:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case []swspec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.SchemaOrArray:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
default:
return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
}
return nil
}
func cloneSchema(schema *swspec.Schema) (*swspec.Schema, error) {
var sch swspec.Schema
if err := swag.FromDynamicJSON(schema, &sch); err != nil {
return nil, fmt.Errorf("name inlined schema: %v", err)
}
return &sch, nil
}
func importExternalReferences(opts *FlattenOpts) error {
groupedRefs := reverseIndexForSchemaRefs(opts)
for refStr, entry := range groupedRefs {
if !entry.Ref.HasFragmentOnly {
if swspec.Debug {
log.Printf("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr)
}
// resolve to actual schema
sch, err := swspec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))
if err != nil {
return err
}
if sch == nil {
return fmt.Errorf("no schema found at %s for [%s]", refStr, strings.Join(entry.Keys, ", "))
}
if swspec.Debug {
log.Printf("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr)
}
// generate a unique name
newName := uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref))
if swspec.Debug {
log.Printf("new name for [%s]: %s", strings.Join(entry.Keys, ", "), newName)
}
// rewrite the external refs to local ones
for _, key := range entry.Keys {
if err := updateRef(opts.Swagger(), key, swspec.MustCreateRef("#"+path.Join("/definitions", newName))); err != nil {
return err
}
}
// add the resolved schema to the definitions
saveSchema(opts.Swagger(), newName, sch)
}
}
return nil
}
type refRevIdx struct {
Ref swspec.Ref
Keys []string
}
func reverseIndexForSchemaRefs(opts *FlattenOpts) map[string]refRevIdx {
collected := make(map[string]refRevIdx)
for key, schRef := range opts.Spec.references.schemas {
if entry, ok := collected[schRef.String()]; ok {
entry.Keys = append(entry.Keys, key)
collected[schRef.String()] = entry
} else {
collected[schRef.String()] = refRevIdx{
Ref: schRef,
Keys: []string{key},
}
}
}
return collected
}
func nameFromRef(ref swspec.Ref) string {
u := ref.GetURL()
if u.Fragment != "" {
return swag.ToJSONName(path.Base(u.Fragment))
}
if u.Path != "" {
bn := path.Base(u.Path)
if bn != "" && bn != "/" {
ext := path.Ext(bn)
if ext != "" {
return swag.ToJSONName(bn[:len(bn)-len(ext)])
}
return swag.ToJSONName(bn)
}
}
return swag.ToJSONName(strings.Replace(u.Host, ".", " ", -1))
}
func saveSchema(spec *swspec.Swagger, name string, schema *swspec.Schema) {
if schema == nil {
return
}
if spec.Definitions == nil {
spec.Definitions = make(map[string]swspec.Schema, 150)
}
spec.Definitions[name] = *schema
}
func updateRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
if swspec.Debug {
log.Printf("updating ref for %s with %s", key, ref.String())
}
pth := key[1:]
ptr, err := jsonpointer.New(pth)
if err != nil {
return err
}
value, _, err := ptr.Get(spec)
if err != nil {
return err
}
switch refable := value.(type) {
case *swspec.Schema:
refable.Ref = ref
case *swspec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case *swspec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case swspec.Schema:
parent, entry := path.Dir(pth), path.Base(pth)
if swspec.Debug {
log.Println("getting schema holder at:", parent)
}
pptr, err := jsonpointer.New(parent)
if err != nil {
return err
}
pvalue, _, err := pptr.Get(spec)
if err != nil {
return fmt.Errorf("can't get parent for %s: %v", parent, err)
}
switch container := pvalue.(type) {
case swspec.Definitions:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case map[string]swspec.Schema:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case []swspec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.SchemaOrArray:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
func containsString(names []string, name string) bool {
for _, nm := range names {
if nm == name {
return true
}
}
return false
}
type opRef struct {
Method string
Path string
Key string
ID string
Op *swspec.Operation
Ref swspec.Ref
}
type opRefs []opRef
func (o opRefs) Len() int { return len(o) }
func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
func gatherOperations(specDoc *Spec, operationIDs []string) map[string]opRef {
var oprefs opRefs
for method, pathItem := range specDoc.Operations() {
for pth, operation := range pathItem {
vv := *operation
oprefs = append(oprefs, opRef{
Key: swag.ToGoName(strings.ToLower(method) + " " + pth),
Method: method,
Path: pth,
ID: vv.ID,
Op: &vv,
Ref: swspec.MustCreateRef("#" + path.Join("/paths", jsonpointer.Escape(pth), method)),
})
}
}
sort.Sort(oprefs)
operations := make(map[string]opRef)
for _, opr := range oprefs {
nm := opr.ID
if nm == "" {
nm = opr.Key
}
oo, found := operations[nm]
if found && oo.Method != opr.Method && oo.Path != opr.Path {
nm = opr.Key
}
if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) {
opr.ID = nm
opr.Op.ID = nm
operations[nm] = opr
}
}
return operations
}