Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/pkg/cluster/validation.go
2501 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cluster
6
7
import (
8
"context"
9
"encoding/json"
10
"fmt"
11
12
corev1 "k8s.io/api/core/v1"
13
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
"k8s.io/apimachinery/pkg/version"
15
"k8s.io/client-go/kubernetes"
16
_ "k8s.io/client-go/plugin/pkg/client/auth" // https://github.com/kubernetes/client-go/issues/242
17
"k8s.io/client-go/rest"
18
)
19
20
type ValidationStatus string
21
22
const (
23
ValidationStatusOk ValidationStatus = "OK"
24
ValidationStatusError ValidationStatus = "ERROR"
25
ValidationStatusWarning ValidationStatus = "WARNING"
26
)
27
28
type ValidationError struct {
29
Message string `json:"message"`
30
Type ValidationStatus `json:"type"`
31
}
32
33
type ValidationCheck struct {
34
Name string `json:"name"`
35
Description string `json:"description"`
36
Check ValidationCheckFunc `json:"-"`
37
}
38
39
type ValidationCheckFunc func(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error)
40
41
type ValidationItem struct {
42
ValidationCheck
43
Status ValidationStatus `json:"status"`
44
Errors []ValidationError `json:"errors,omitempty"` // Only populated if present
45
}
46
47
type ValidationResult struct {
48
Status ValidationStatus `json:"status"`
49
Items []ValidationItem `json:"items"`
50
}
51
52
// ClusterChecks are checks against for a cluster
53
var ClusterChecks = ValidationChecks{
54
{
55
Name: "Linux kernel version",
56
Description: "all cluster nodes run Linux " + kernelVersionConstraint,
57
Check: checkKernelVersion,
58
},
59
{
60
Name: "containerd enabled",
61
Check: checkContainerDRuntime,
62
Description: "all cluster nodes run containerd",
63
},
64
{
65
Name: "Kubernetes version",
66
Description: "all cluster nodes run kubernetes version " + kubernetesVersionConstraint,
67
Check: checkKubernetesVersion,
68
},
69
{
70
Name: "cert-manager installed",
71
Check: checkCertManagerInstalled,
72
Description: "cert-manager is installed and has available issuer",
73
},
74
{
75
Name: "Namespace exists",
76
Description: "ensure that the target namespace exists",
77
Check: checkNamespaceExists,
78
},
79
}
80
81
// ValidationChecks are a group of validations
82
type ValidationChecks []ValidationCheck
83
84
func (v ValidationChecks) Len() int { return len(v) }
85
86
// Validate runs the checks
87
func (checks ValidationChecks) Validate(ctx context.Context, config *rest.Config, namespace string) (*ValidationResult, error) {
88
results := &ValidationResult{
89
Status: ValidationStatusOk,
90
Items: []ValidationItem{},
91
}
92
93
client, err := kubernetes.NewForConfig(config)
94
if err != nil {
95
return nil, err
96
}
97
ctx = context.WithValue(ctx, keyClientset, client)
98
99
list, err := ListNodesFromContext(ctx, config)
100
if err != nil {
101
return nil, err
102
}
103
ctx = context.WithValue(ctx, keyNodeList, list)
104
105
for _, check := range checks {
106
result := ValidationItem{
107
ValidationCheck: check,
108
Status: ValidationStatusOk,
109
Errors: []ValidationError{},
110
}
111
112
res, err := check.Check(ctx, config, namespace)
113
if err != nil {
114
return nil, err
115
}
116
for _, resultErr := range res {
117
switch resultErr.Type {
118
case ValidationStatusError:
119
// Any error always changes status
120
result.Status = ValidationStatusError
121
results.Status = ValidationStatusError
122
case ValidationStatusWarning:
123
// Only put to warning if status is ok
124
if result.Status == ValidationStatusOk {
125
result.Status = ValidationStatusWarning
126
}
127
if results.Status == ValidationStatusOk {
128
results.Status = ValidationStatusWarning
129
}
130
}
131
132
result.Errors = append(result.Errors, resultErr)
133
}
134
135
results.Items = append(results.Items, result)
136
}
137
138
return results, nil
139
}
140
141
const (
142
keyNodeList = "nodeListKey"
143
keyClientset = "clientset"
144
)
145
146
func ListNodesFromContext(ctx context.Context, config *rest.Config) ([]corev1.Node, error) {
147
client, err := clientsetFromContext(ctx, config)
148
if err != nil {
149
return nil, err
150
}
151
152
val := ctx.Value(keyNodeList)
153
if res, ok := val.([]corev1.Node); ok && res != nil {
154
return res, nil
155
}
156
157
nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
158
if err != nil {
159
return nil, err
160
}
161
return nodes.Items, nil
162
}
163
164
func clientsetFromContext(ctx context.Context, config *rest.Config) (kubernetes.Interface, error) {
165
val := ctx.Value(keyClientset)
166
if res, ok := val.(kubernetes.Interface); ok && res != nil {
167
return res, nil
168
}
169
return kubernetes.NewForConfig(config)
170
}
171
172
func serverVersion(ctx context.Context, config *rest.Config) (*version.Info, error) {
173
client, err := clientsetFromContext(ctx, config)
174
if err != nil {
175
return nil, err
176
}
177
body, err := client.CoreV1().RESTClient().Get().AbsPath("/version").Do(ctx).Raw()
178
if err != nil {
179
return nil, err
180
}
181
var info version.Info
182
err = json.Unmarshal(body, &info)
183
if err != nil {
184
return nil, fmt.Errorf("unable to parse the server version: %v", err)
185
}
186
return &info, nil
187
}
188
189