forked from projectcalico/calico
-
Notifications
You must be signed in to change notification settings - Fork 0
/
customresource.go
172 lines (149 loc) · 5.69 KB
/
customresource.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (c) 2017 Tigera, Inc. All rights reserved.
// 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 resources
import (
"context"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/dtest11/calico/libcalico-go/lib/backend/model"
"github.com/dtest11/calico/libcalico-go/lib/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
// Interface required to satisfy use as a Kubernetes Custom Resource type.
type CustomK8sResource interface {
runtime.Object
metav1.ObjectMetaAccessor
}
// Interface required to satisfy use as a Kubernetes Custom Resource List type.
type CustomK8sList interface {
runtime.Object
metav1.ListMetaAccessor
}
// CustomK8sResourceConverter defines an interface to map between KVPair representation
// and a custom Kubernetes resource.
type CustomK8sResourceConverter interface {
// ListInterfaceToKey converts a ListInterface to a Key if the
// ListInterface specifies a specific instance, otherwise returns nil.
ListInterfaceToKey(model.ListInterface) model.Key
// Convert the Key to the Resource name.
KeyToName(model.Key) (string, error)
// Convert the Resource name to the Key.
NameToKey(string) (model.Key, error)
// Convert the Resource to a KVPair.
ToKVPair(CustomK8sResource) (*model.KVPair, error)
// Convert a KVPair to a Resource.
FromKVPair(*model.KVPair) (CustomK8sResource, error)
}
// customK8sResourceClient implements the K8sResourceClient interface and provides a generic
// mechanism for a 1:1 mapping between a Calico Resource and an equivalent Kubernetes
// custom resource type.
type customK8sResourceClient struct {
clientSet *kubernetes.Clientset
restClient *rest.RESTClient
name string
resource string
description string
k8sResourceType reflect.Type
k8sListType reflect.Type
converter CustomK8sResourceConverter
}
// Get gets an existing Custom K8s Resource instance in the k8s API using the supplied Key.
func (c *customK8sResourceClient) Get(key model.Key) (*model.KVPair, error) {
logContext := log.WithFields(log.Fields{
"Key": key,
"Resource": c.resource,
})
logContext.Debug("Get custom Kubernetes resource")
name, err := c.converter.KeyToName(key)
if err != nil {
logContext.WithError(err).Info("Error getting resource")
return nil, err
}
// Add the name to the log context now that we know it, and query
// Kubernetes.
logContext = logContext.WithField("Name", name)
logContext.Debug("Get custom Kubernetes resource by name")
resOut := reflect.New(c.k8sResourceType).Interface().(CustomK8sResource)
err = c.restClient.Get().
Resource(c.resource).
Name(name).
Do(context.Background()).Into(resOut)
if err != nil {
logContext.WithError(err).Info("Error getting resource")
return nil, K8sErrorToCalico(err, key)
}
return c.converter.ToKVPair(resOut)
}
// List lists configured Custom K8s Resource instances in the k8s API matching the
// supplied ListInterface.
func (c *customK8sResourceClient) List(list model.ListInterface) ([]*model.KVPair, string, error) {
logContext := log.WithFields(log.Fields{
"ListInterface": list,
"Resource": c.resource,
})
logContext.Debug("List Custom K8s Resource")
kvps := []*model.KVPair{}
// Attempt to convert the ListInterface to a Key. If possible, the parameters
// indicate a fully qualified resource, and we'll need to use Get instead of
// List.
if key := c.converter.ListInterfaceToKey(list); key != nil {
logContext.Debug("Performing List using Get")
if kvp, err := c.Get(key); err != nil {
// The error will already be a Calico error type. Ignore
// error that it doesn't exist - we'll return an empty
// list.
if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok {
log.WithField("Resource", c.resource).WithError(err).Info("Error listing resource")
return nil, "", err
}
return kvps, "", nil
} else {
kvps = append(kvps, kvp)
return kvps, kvp.Revision, nil
}
}
// Since we are not performing an exact Get, Kubernetes will return a
// list of resources.
reslOut := reflect.New(c.k8sListType).Interface().(CustomK8sList)
// Perform the request.
err := c.restClient.Get().
Resource(c.resource).
Do(context.Background()).Into(reslOut)
if err != nil {
// Don't return errors for "not found". This just
// means there are no matching Custom K8s Resources, and we should return
// an empty list.
if !kerrors.IsNotFound(err) {
log.WithError(err).Info("Error listing resources")
return nil, "", K8sErrorToCalico(err, list)
}
return kvps, reslOut.GetListMeta().GetResourceVersion(), nil
}
// We expect the list type to have an "Items" field that we can
// iterate over.
elem := reflect.ValueOf(reslOut).Elem()
items := reflect.ValueOf(elem.FieldByName("Items").Interface())
for idx := 0; idx < items.Len(); idx++ {
res := items.Index(idx).Addr().Interface().(CustomK8sResource)
if kvp, err := c.converter.ToKVPair(res); err == nil {
kvps = append(kvps, kvp)
} else {
logContext.WithError(err).WithField("Item", res).Warning("unable to process resource, skipping")
}
}
return kvps, reslOut.GetListMeta().GetResourceVersion(), nil
}