1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package openapi
18
19import (
20	openapi_v2 "github.com/googleapis/gnostic/openapiv2"
21
22	"k8s.io/apimachinery/pkg/runtime/schema"
23	"k8s.io/kube-openapi/pkg/util/proto"
24)
25
26// Resources interface describe a resources provider, that can give you
27// resource based on group-version-kind.
28type Resources interface {
29	LookupResource(gvk schema.GroupVersionKind) proto.Schema
30}
31
32// groupVersionKindExtensionKey is the key used to lookup the
33// GroupVersionKind value for an object definition from the
34// definition's "extensions" map.
35const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
36
37// document is an implementation of `Resources`. It looks for
38// resources in an openapi Schema.
39type document struct {
40	// Maps gvk to model name
41	resources map[schema.GroupVersionKind]string
42	models    proto.Models
43}
44
45var _ Resources = &document{}
46
47// NewOpenAPIData creates a new `Resources` out of the openapi document
48func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
49	models, err := proto.NewOpenAPIData(doc)
50	if err != nil {
51		return nil, err
52	}
53
54	resources := map[schema.GroupVersionKind]string{}
55	for _, modelName := range models.ListModels() {
56		model := models.LookupModel(modelName)
57		if model == nil {
58			panic("ListModels returns a model that can't be looked-up.")
59		}
60		gvkList := parseGroupVersionKind(model)
61		for _, gvk := range gvkList {
62			if len(gvk.Kind) > 0 {
63				resources[gvk] = modelName
64			}
65		}
66	}
67
68	return &document{
69		resources: resources,
70		models:    models,
71	}, nil
72}
73
74func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
75	modelName, found := d.resources[gvk]
76	if !found {
77		return nil
78	}
79	return d.models.LookupModel(modelName)
80}
81
82// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
83func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
84	extensions := s.GetExtensions()
85
86	gvkListResult := []schema.GroupVersionKind{}
87
88	// Get the extensions
89	gvkExtension, ok := extensions[groupVersionKindExtensionKey]
90	if !ok {
91		return []schema.GroupVersionKind{}
92	}
93
94	// gvk extension must be a list of at least 1 element.
95	gvkList, ok := gvkExtension.([]interface{})
96	if !ok {
97		return []schema.GroupVersionKind{}
98	}
99
100	for _, gvk := range gvkList {
101		// gvk extension list must be a map with group, version, and
102		// kind fields
103		gvkMap, ok := gvk.(map[interface{}]interface{})
104		if !ok {
105			continue
106		}
107		group, ok := gvkMap["group"].(string)
108		if !ok {
109			continue
110		}
111		version, ok := gvkMap["version"].(string)
112		if !ok {
113			continue
114		}
115		kind, ok := gvkMap["kind"].(string)
116		if !ok {
117			continue
118		}
119
120		gvkListResult = append(gvkListResult, schema.GroupVersionKind{
121			Group:   group,
122			Version: version,
123			Kind:    kind,
124		})
125	}
126
127	return gvkListResult
128}
129