-
Notifications
You must be signed in to change notification settings - Fork 68
/
walker.go
180 lines (153 loc) · 6.02 KB
/
walker.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
173
174
175
176
177
178
179
180
package kubepug
import (
"context"
"fmt"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
log "github.com/sirupsen/logrus"
)
// ignoreStruct is an empty map, the key is the API Group to be ignored. No value exists
type ignoreStruct map[string]struct{}
const crdGroup = "apiextensions.k8s.io"
const apiRegistration = "apiregistration.k8s.io"
// This function will receive an apiExtension (CRD) and populate it into the struct to be verified later
func (ignoreStruct ignoreStruct) populateCRDGroups(dynClient dynamic.Interface, version string) {
log.Debugf("Populating CRDs array of version %s", version)
crdgvr := schema.GroupVersionResource{
Group: crdGroup,
Version: version,
Resource: "customresourcedefinitions",
}
crdList, err := dynClient.Resource(crdgvr).List(context.TODO(), metav1.ListOptions{})
if apierrors.IsNotFound(err) {
return
}
if err != nil {
log.Fatalf("Failed to connect to K8s cluster to List CRDs")
}
// We'll create an empty map[crd] because that's easier than keep interating into an array/slice to find a value
var empty struct{}
for _, d := range crdList.Items {
group, found, err := unstructured.NestedString(d.Object, "spec", "group")
// No group fields found, move on!
if err != nil || !found {
continue
}
if _, ok := ignoreStruct[group]; !ok {
ignoreStruct[group] = empty
}
}
}
// This function will receive an apiRegistration (APIService) and populate it into the struct
// to be verified later. It will consider only if the field "service" is not null
// representing an external Service
func (ignoreStruct ignoreStruct) populateAPIService(dynClient dynamic.Interface, version string) {
log.Debugf("Populating APIService array of version %s", version)
apisvcgvr := schema.GroupVersionResource{
Group: apiRegistration,
Version: version,
Resource: "apiservices",
}
apisvcList, err := dynClient.Resource(apisvcgvr).List(context.TODO(), metav1.ListOptions{})
if apierrors.IsNotFound(err) {
return
}
if err != nil {
log.Fatalf("Failed to connect to K8s cluster to List API Services")
}
// We'll create an empty map[crd] because that's easier than keep interating into an array/slice to find a value
var empty struct{}
for _, d := range apisvcList.Items {
_, foundSvc, errSvc := unstructured.NestedString(d.Object, "spec", "service", "name")
group, foundGrp, errGrp := unstructured.NestedString(d.Object, "spec", "group")
// No services fields or group field found, move on!
if errSvc != nil || !foundSvc || errGrp != nil || !foundGrp {
continue
}
if _, ok := ignoreStruct[group]; !ok {
ignoreStruct[group] = empty
}
}
}
// WalkObjects walk through Kubernetes API and verifies which Resources doesn't exists anymore in swagger.json
func (KubernetesAPIs KubernetesAPIs) WalkObjects(config *rest.Config) (deleted []DeletedAPI) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
log.Fatalf("Failed to create the K8s Discovery client")
}
log.Debug("Getting all the Server Resources")
resourcesList, err := discoveryClient.ServerResources()
if err != nil {
if apierrors.IsForbidden(err) {
log.Fatalf("Failed to list Server Resources. Permission denied! Please check if you have the proper authorization")
}
log.Fatalf("Failed communicating with k8s while discovering server resources. \nError: %v", err)
}
dynClient, err := dynamic.NewForConfig(config)
if err != nil {
log.Fatalf("Failed to create dynamic client. \nError: %v", err)
}
var ignoreObjects ignoreStruct = make(map[string]struct{})
// Discovery CRDs versions to populate CRDs
for _, resources := range resourcesList {
if strings.Contains(resources.GroupVersion, crdGroup) {
version := strings.Split(resources.GroupVersion, "/")[1]
ignoreObjects.populateCRDGroups(dynClient, version)
}
if strings.Contains(resources.GroupVersion, apiRegistration) {
version := strings.Split(resources.GroupVersion, "/")[1]
ignoreObjects.populateAPIService(dynClient, version)
}
}
log.Debugf("Walking through %d resource types", len(resourcesList))
for _, resourceGroupVersion := range resourcesList {
// We dont want CRDs or APIExtensions to be walked
if _, ok := ignoreObjects[strings.Split(resourceGroupVersion.GroupVersion, "/")[0]]; ok {
continue
}
for i := range resourceGroupVersion.APIResources {
resource := &resourceGroupVersion.APIResources[i]
// We don't want to check subObjects (like pods/status)
if len(strings.Split(resource.Name, "/")) != 1 {
continue
}
keyAPI := fmt.Sprintf("%s/%s", resourceGroupVersion.GroupVersion, resource.Name)
if _, ok := KubernetesAPIs[keyAPI]; !ok {
gv, err := schema.ParseGroupVersion(resourceGroupVersion.GroupVersion)
if err != nil {
log.Fatalf("Failed to Parse GroupVersion of Resource")
}
gvr := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: resource.Name}
list, err := dynClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
if apierrors.IsNotFound(err) || apierrors.IsMethodNotSupported(err) {
continue
}
if apierrors.IsForbidden(err) {
log.Fatalf("Failed to list Server Resources of type %s/%s/%s. Permission denied! Please check if you have the proper authorization", gv.Group, gv.Version, resource.Name)
}
if err != nil {
log.Fatalf("Failed to List objects of type %s/%s/%s. \nError: %v", gv.Group, gv.Version, resource.Name, err)
}
if len(list.Items) > 0 {
log.Debugf("Found %d deleted items in %s/%s", len(list.Items), gvr.GroupResource().String(), resource.Name)
d := DeletedAPI{
Deleted: true,
Name: resource.Name,
Group: gvr.GroupResource().String(),
Kind: resource.Kind,
Version: gv.Version,
}
d.Items = listObjects(list.Items)
deleted = append(deleted, d)
}
}
}
}
return deleted
}