1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package crdloader
16
17 import (
18 "context"
19 "fmt"
20 "io/ioutil"
21 "path"
22 "strings"
23
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"
27
28 "github.com/ghodss/yaml"
29 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 "k8s.io/apimachinery/pkg/types"
33 "sigs.k8s.io/controller-runtime/pkg/client"
34 )
35
36 type CrdLoader struct {
37 kubeClient client.Client
38 }
39
40 func New(kubeClient client.Client) *CrdLoader {
41 return &CrdLoader{
42 kubeClient: kubeClient,
43 }
44 }
45
46
47 func (l *CrdLoader) GetCRDForKind(kind string) (*apiextensions.CustomResourceDefinition, error) {
48 return l.GetCRD("", "", kind)
49 }
50
51
52 func (l *CrdLoader) GetCRDForGVK(gvk schema.GroupVersionKind) (*apiextensions.CustomResourceDefinition, error) {
53 return l.GetCRD(gvk.Group, gvk.Version, gvk.Kind)
54 }
55
56
57 func (l *CrdLoader) GetCRD(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
58 if kind == "" {
59 return nil, fmt.Errorf("invalid argument: 'kind' must contain a value")
60 }
61 if group == "" || version == "" {
62 return l.getCRDViaList(group, version, kind)
63 }
64 return l.getCRDViaGet(group, version, kind)
65 }
66
67 func (l *CrdLoader) getCRDViaList(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
68 listOptions := client.ListOptions{
69 Raw: &metav1.ListOptions{},
70 }
71 crds := make([]apiextensions.CustomResourceDefinition, 0)
72 for ok := true; ok; ok = listOptions.Raw.Continue != "" {
73 var list apiextensions.CustomResourceDefinitionList
74 if err := l.kubeClient.List(context.TODO(), &list, &listOptions); err != nil {
75 return nil, fmt.Errorf("error listing CRDs for GVK %v: %v", formatGVK(group, version, kind), err)
76 }
77 crds = append(crds, list.Items...)
78 listOptions.Raw.Continue = list.Continue
79 }
80 return getMatchingCRD(group, version, kind, crds)
81 }
82
83 func (l *CrdLoader) getCRDViaGet(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
84 lowercasePluralKind := strings.ToLower(text.Pluralize(kind))
85 var crd apiextensions.CustomResourceDefinition
86 nn := types.NamespacedName{Name: fmt.Sprintf("%v.%v", lowercasePluralKind, group)}
87 if err := l.kubeClient.Get(context.TODO(), nn, &crd); err != nil {
88 return nil, fmt.Errorf("error getting CRD for GVK %v: %v", formatGVK(group, version, kind), err)
89 }
90 return &crd, nil
91 }
92
93
94 func GetCRDForKind(kind string) (*apiextensions.CustomResourceDefinition, error) {
95 return GetCRD("", "", kind)
96 }
97
98
99 func GetCRDForGVK(gvk schema.GroupVersionKind) (*apiextensions.CustomResourceDefinition, error) {
100 return GetCRD(gvk.Group, gvk.Version, gvk.Kind)
101 }
102
103
104 func GetCRD(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
105 crds, err := LoadCRDs()
106 if err != nil {
107 return nil, fmt.Errorf("error loading CRDs: %v", err)
108 }
109 return getMatchingCRD(group, version, kind, crds)
110 }
111
112 func getMatchingCRD(group, version, kind string, crds []apiextensions.CustomResourceDefinition) (*apiextensions.CustomResourceDefinition, error) {
113 var match *apiextensions.CustomResourceDefinition
114 for _, crd := range crds {
115 if isMatch(group, version, kind, crd) {
116 if match == nil {
117 crd := crd
118 match = &crd
119 } else {
120 return nil, fmt.Errorf("ambiguous result: multiple CRDs match GVK parameter of %v", formatGVK(group, version, kind))
121 }
122 }
123 }
124 if match == nil {
125 return nil, fmt.Errorf("no CRD matches GVK parameter of %v", formatGVK(group, version, kind))
126 }
127 return match, nil
128 }
129
130 func isMatch(group, version, kind string, crd apiextensions.CustomResourceDefinition) bool {
131 if crd.Spec.Names.Kind != kind {
132 return false
133 }
134 if group != "" {
135 if crd.Spec.Group != group {
136 return false
137 }
138 }
139 if version != "" {
140 if k8s.GetVersionFromCRD(&crd) != version {
141 return false
142 }
143 }
144 return true
145 }
146
147 func formatGVK(group, version, kind string) string {
148 if group == "" {
149 group = "nil"
150 }
151 if version == "" {
152 version = "nil"
153 }
154 return fmt.Sprintf("{%v, %v, %v}", group, version, kind)
155 }
156
157 func LoadCRDs() ([]apiextensions.CustomResourceDefinition, error) {
158 crdsRoot := repo.GetCRDsPath()
159 files, err := ioutil.ReadDir(crdsRoot)
160 if err != nil {
161 return nil, fmt.Errorf("error listing directory '%v': %v", crdsRoot, err)
162 }
163 results := make([]apiextensions.CustomResourceDefinition, 0)
164 for _, crdFile := range files {
165 crd, err := FileToCRD(path.Join(crdsRoot, crdFile.Name()))
166 if err != nil {
167 return nil, err
168 }
169 results = append(results, *crd)
170 }
171 return results, nil
172 }
173
174 func FileToCRD(fileName string) (*apiextensions.CustomResourceDefinition, error) {
175 bytes, err := ioutil.ReadFile(fileName)
176 if err != nil {
177 return nil, fmt.Errorf("error reading file '%v': %v", fileName, err)
178 }
179 var crd apiextensions.CustomResourceDefinition
180 err = yaml.Unmarshal(bytes, &crd)
181 if err != nil {
182 return nil, fmt.Errorf("error unmarshalling '%v' to CRD: %v", fileName, err)
183 }
184 return &crd, nil
185 }
186
View as plain text