-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
kind_provider.go
217 lines (182 loc) · 6.87 KB
/
kind_provider.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
Copyright 2020 The Kubernetes Authors.
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 bootstrap
import (
"context"
"fmt"
"os"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
kindv1 "sigs.k8s.io/kind/pkg/apis/config/v1alpha4"
kind "sigs.k8s.io/kind/pkg/cluster"
"sigs.k8s.io/kind/pkg/cmd"
"sigs.k8s.io/kind/pkg/exec"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/test/framework/internal/log"
)
const (
// DefaultNodeImageRepository is the default node image repository to be used for testing.
DefaultNodeImageRepository = "kindest/node"
// DefaultNodeImageVersion is the default Kubernetes version to be used for creating a kind cluster.
DefaultNodeImageVersion = "v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72"
)
// KindClusterOption is a NewKindClusterProvider option.
type KindClusterOption interface {
apply(*KindClusterProvider)
}
type kindClusterOptionAdapter func(*KindClusterProvider)
func (adapter kindClusterOptionAdapter) apply(kindClusterProvider *KindClusterProvider) {
adapter(kindClusterProvider)
}
// WithNodeImage implements a New Option that instruct the kindClusterProvider to use a specific node image / Kubernetes version.
func WithNodeImage(image string) KindClusterOption {
return kindClusterOptionAdapter(func(k *KindClusterProvider) {
k.nodeImage = image
})
}
// WithDockerSockMount implements a New Option that instruct the kindClusterProvider to mount /var/run/docker.sock into
// the new kind cluster.
func WithDockerSockMount() KindClusterOption {
return kindClusterOptionAdapter(func(k *KindClusterProvider) {
k.withDockerSock = true
})
}
// WithIPv6Family implements a New Option that instruct the kindClusterProvider to set the IPFamily to IPv6 in
// the new kind cluster.
func WithIPv6Family() KindClusterOption {
return kindClusterOptionAdapter(func(k *KindClusterProvider) {
k.ipFamily = clusterv1.IPv6IPFamily
})
}
// WithDualStackFamily implements a New Option that instruct the kindClusterProvider to set the IPFamily to dual in
// the new kind cluster.
func WithDualStackFamily() KindClusterOption {
return kindClusterOptionAdapter(func(k *KindClusterProvider) {
k.ipFamily = clusterv1.DualStackIPFamily
})
}
// LogFolder implements a New Option that instruct the kindClusterProvider to dump bootstrap logs in a folder in case of errors.
func LogFolder(path string) KindClusterOption {
return kindClusterOptionAdapter(func(k *KindClusterProvider) {
k.logFolder = path
})
}
// NewKindClusterProvider returns a ClusterProvider that can create a kind cluster.
func NewKindClusterProvider(name string, options ...KindClusterOption) *KindClusterProvider {
Expect(name).ToNot(BeEmpty(), "name is required for NewKindClusterProvider")
clusterProvider := &KindClusterProvider{
name: name,
}
for _, option := range options {
option.apply(clusterProvider)
}
return clusterProvider
}
// KindClusterProvider implements a ClusterProvider that can create a kind cluster.
type KindClusterProvider struct {
name string
withDockerSock bool
kubeconfigPath string
nodeImage string
ipFamily clusterv1.ClusterIPFamily
logFolder string
}
// Create a Kubernetes cluster using kind.
func (k *KindClusterProvider) Create(ctx context.Context) {
Expect(ctx).NotTo(BeNil(), "ctx is required for Create")
// Sets the kubeconfig path to a temp file.
// NB. the ClusterProvider is responsible for the cleanup of this file
f, err := os.CreateTemp("", "e2e-kind")
Expect(err).ToNot(HaveOccurred(), "Failed to create kubeconfig file for the kind cluster %q", k.name)
k.kubeconfigPath = f.Name()
// Creates the kind cluster
k.createKindCluster()
}
// createKindCluster calls the kind library taking care of passing options for:
// - use a dedicated kubeconfig file (test should not alter the user environment)
// - if required, mount /var/run/docker.sock.
func (k *KindClusterProvider) createKindCluster() {
kindCreateOptions := []kind.CreateOption{
kind.CreateWithKubeconfigPath(k.kubeconfigPath),
}
cfg := &kindv1.Cluster{
TypeMeta: kindv1.TypeMeta{
APIVersion: "kind.x-k8s.io/v1alpha4",
Kind: "Cluster",
},
}
if k.ipFamily == clusterv1.IPv6IPFamily {
cfg.Networking.IPFamily = kindv1.IPv6Family
}
if k.ipFamily == clusterv1.DualStackIPFamily {
cfg.Networking.IPFamily = kindv1.DualStackFamily
}
kindv1.SetDefaultsCluster(cfg)
if k.withDockerSock {
setDockerSockConfig(cfg)
}
kindCreateOptions = append(kindCreateOptions, kind.CreateWithV1Alpha4Config(cfg))
nodeImage := fmt.Sprintf("%s:%s", DefaultNodeImageRepository, DefaultNodeImageVersion)
if k.nodeImage != "" {
nodeImage = k.nodeImage
}
kindCreateOptions = append(
kindCreateOptions,
kind.CreateWithNodeImage(nodeImage),
kind.CreateWithRetain(true))
provider := kind.NewProvider(kind.ProviderWithLogger(cmd.NewLogger()))
err := provider.Create(k.name, kindCreateOptions...)
if err != nil {
// if requested, dump kind logs
if k.logFolder != "" {
if err := provider.CollectLogs(k.name, k.logFolder); err != nil {
log.Logf("Failed to collect logs from kind: %v", err)
}
}
errStr := fmt.Sprintf("Failed to create kind cluster %q: %v", k.name, err)
// Extract the details of the RunError, if the cluster creation was triggered by a RunError.
var runErr *exec.RunError
if errors.As(err, &runErr) {
errStr += "\n" + string(runErr.Output)
}
Expect(err).ToNot(HaveOccurred(), errStr)
}
}
// setDockerSockConfig returns a kind config for mounting /var/run/docker.sock into the kind node.
func setDockerSockConfig(cfg *kindv1.Cluster) {
cfg.Nodes = []kindv1.Node{
{
Role: kindv1.ControlPlaneRole,
ExtraMounts: []kindv1.Mount{
{
HostPath: "/var/run/docker.sock",
ContainerPath: "/var/run/docker.sock",
},
},
},
}
}
// GetKubeconfigPath returns the path to the kubeconfig file for the cluster.
func (k *KindClusterProvider) GetKubeconfigPath() string {
return k.kubeconfigPath
}
// Dispose the kind cluster and its kubeconfig file.
func (k *KindClusterProvider) Dispose(ctx context.Context) {
Expect(ctx).NotTo(BeNil(), "ctx is required for Dispose")
if err := kind.NewProvider().Delete(k.name, k.kubeconfigPath); err != nil {
log.Logf("Deleting the kind cluster %q failed. You may need to remove this by hand.", k.name)
}
if err := os.Remove(k.kubeconfigPath); err != nil {
log.Logf("Deleting the kubeconfig file %q file. You may need to remove this by hand.", k.kubeconfigPath)
}
}