Skip to content

Commit 1e1afca

Browse files
maru-avacam-schultz
authored andcommitted
[tmpnet] Start kind cluster with golang (#3780)
1 parent 303d84f commit 1e1afca

File tree

8 files changed

+139
-35
lines changed

8 files changed

+139
-35
lines changed

bin/tmpnetctl

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ cd "${AVALANCHE_PATH}"
1010
if [[ ! -f ./build/tmpnetctl ]]; then
1111
./scripts/build_tmpnetctl.sh
1212
fi
13-
./build/tmpnetctl
13+
./build/tmpnetctl "${@}"

scripts/tests.e2e.bootstrap_monitor.sh

+1-9
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@ if ! [[ "$0" =~ scripts/tests.e2e.bootstrap_monitor.sh ]]; then
99
exit 255
1010
fi
1111

12-
CMD=kind-with-registry.sh
13-
14-
if ! command -v "${CMD}" &> /dev/null; then
15-
echo "kind-with-registry.sh not found, have you run 'nix develop'?"
16-
echo "To install nix: https://github.com/DeterminateSystems/nix-installer?tab=readme-ov-file#install-nix"
17-
exit 1
18-
fi
19-
20-
"${CMD}"
12+
./bin/tmpnetctl start-kind-cluster
2113

2214
KUBECONFIG="$HOME/.kube/config" ./bin/ginkgo -v ./tests/fixture/bootstrapmonitor/e2e

tests/fixture/bootstrapmonitor/common.go

+2-23
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ import (
1717
"go.uber.org/zap"
1818
"k8s.io/apimachinery/pkg/types"
1919
"k8s.io/client-go/kubernetes"
20-
"k8s.io/client-go/tools/clientcmd"
2120

2221
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
2322
"github.com/ava-labs/avalanchego/utils/logging"
2423
"github.com/ava-labs/avalanchego/version"
2524

2625
corev1 "k8s.io/api/core/v1"
2726
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28-
restclient "k8s.io/client-go/rest"
2927
)
3028

3129
// Path to write the details to on the data volume
@@ -223,25 +221,6 @@ func getLatestImageDetails(
223221

224222
func getClientset(log logging.Logger) (*kubernetes.Clientset, error) {
225223
log.Info("Initializing clientset")
226-
kubeconfigPath := os.Getenv("KUBECONFIG")
227-
var (
228-
kubeconfig *restclient.Config
229-
err error
230-
)
231-
if len(kubeconfigPath) > 0 {
232-
// Only use BuildConfigFromFlags if a path is provided to avoid the warning logs that
233-
// will be omitted in a format that differs from the avalanchego format.
234-
if kubeconfig, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath); err != nil {
235-
return nil, fmt.Errorf("failed to build kubeconfig: %w", err)
236-
}
237-
} else {
238-
if kubeconfig, err = restclient.InClusterConfig(); err != nil {
239-
return nil, fmt.Errorf("failed to build kubeconfig: %w", err)
240-
}
241-
}
242-
clientset, err := kubernetes.NewForConfig(kubeconfig)
243-
if err != nil {
244-
return nil, fmt.Errorf("failed to create clientset: %w", err)
245-
}
246-
return clientset, nil
224+
kubeConfigPath := os.Getenv("KUBECONFIG")
225+
return tmpnet.GetClientset(log, kubeConfigPath, "")
247226
}

tests/fixture/bootstrapmonitor/e2e/e2e_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/stretchr/testify/require"
1818
"go.uber.org/zap"
1919
"k8s.io/client-go/kubernetes"
20-
"k8s.io/client-go/tools/clientcmd"
2120

2221
"github.com/ava-labs/avalanchego/config"
2322
"github.com/ava-labs/avalanchego/ids"
@@ -105,7 +104,7 @@ var _ = ginkgo.Describe("[Bootstrap Tester]", func() {
105104

106105
ginkgo.By("Configuring a kubernetes client")
107106
kubeconfigPath := os.Getenv("KUBECONFIG")
108-
kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
107+
kubeconfig, err := tmpnet.GetClientConfig(tc.Log(), kubeconfigPath, "")
109108
require.NoError(err)
110109
clientset, err := kubernetes.NewForConfig(kubeconfig)
111110
require.NoError(err)

tests/fixture/tmpnet/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ the following non-test files:
4141
| node.go | Node | Orchestrates and configures nodes |
4242
| node_config.go | Node | Reads and writes node configuration |
4343
| node_process.go | NodeProcess | Orchestrates node processes |
44+
| start_kind_cluster.go | | Starts a local kind cluster |
4445
| subnet.go | Subnet | Orchestrates subnets |
4546
| utils.go | | Defines shared utility functions |
4647

tests/fixture/tmpnet/cmd/main.go

+36
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path/filepath"
1313

1414
"github.com/spf13/cobra"
15+
"github.com/spf13/pflag"
1516
"go.uber.org/zap"
1617

1718
"github.com/ava-labs/avalanchego/tests"
@@ -236,9 +237,44 @@ func main() {
236237
)
237238
rootCmd.AddCommand(checkLogsCmd)
238239

240+
var (
241+
kubeConfigPath string
242+
kubeConfigContext string
243+
)
244+
startKindClusterCmd := &cobra.Command{
245+
Use: "start-kind-cluster",
246+
Short: "Starts a local kind cluster with an integrated registry",
247+
RunE: func(*cobra.Command, []string) error {
248+
ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout)
249+
defer cancel()
250+
log, err := tests.LoggerForFormat("", rawLogFormat)
251+
if err != nil {
252+
return err
253+
}
254+
return tmpnet.StartKindCluster(ctx, log, kubeConfigPath, kubeConfigContext)
255+
},
256+
}
257+
SetKubeConfigFlags(startKindClusterCmd.PersistentFlags(), &kubeConfigPath, &kubeConfigContext)
258+
rootCmd.AddCommand(startKindClusterCmd)
259+
239260
if err := rootCmd.Execute(); err != nil {
240261
fmt.Fprintf(os.Stderr, "tmpnetctl failed: %v\n", err)
241262
os.Exit(1)
242263
}
243264
os.Exit(0)
244265
}
266+
267+
func SetKubeConfigFlags(flagSet *pflag.FlagSet, kubeConfigPath *string, kubeConfigContext *string) {
268+
flagSet.StringVar(
269+
kubeConfigPath,
270+
"kubeconfig",
271+
os.Getenv("KUBECONFIG"),
272+
"The path to a kubernetes configuration file for the target cluster",
273+
)
274+
flagSet.StringVar(
275+
kubeConfigContext,
276+
"kubeconfig-context",
277+
"",
278+
"The path to a kubernetes configuration file for the target cluster",
279+
)
280+
}

tests/fixture/tmpnet/kube.go

+41
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"k8s.io/apimachinery/pkg/util/intstr"
1919
"k8s.io/apimachinery/pkg/util/wait"
2020
"k8s.io/client-go/kubernetes"
21+
"k8s.io/client-go/tools/clientcmd"
2122
"k8s.io/client-go/tools/portforward"
2223
"k8s.io/client-go/transport/spdy"
2324
"k8s.io/utils/pointer"
@@ -299,3 +300,43 @@ func enableLocalForwardForPod(
299300
}
300301
return forwardedPorts[0].Local, stopChan, nil
301302
}
303+
304+
// GetClientConfig replicates the behavior of clientcmd.BuildConfigFromFlags with zap logging and
305+
// support for an optional config context. If path is not provided, use of in-cluster config will
306+
// be attempted.
307+
func GetClientConfig(log logging.Logger, path string, context string) (*restclient.Config, error) {
308+
if len(path) == 0 {
309+
log.Warn("--kubeconfig not set. Using the inClusterConfig. This might not work.")
310+
kubeconfig, err := restclient.InClusterConfig()
311+
if err == nil {
312+
return kubeconfig, nil
313+
}
314+
log.Warn("failed to create inClusterConfig, falling back to default config",
315+
zap.Error(err),
316+
)
317+
}
318+
overrides := &clientcmd.ConfigOverrides{}
319+
if len(context) > 0 {
320+
overrides.CurrentContext = context
321+
}
322+
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
323+
&clientcmd.ClientConfigLoadingRules{
324+
ExplicitPath: path,
325+
},
326+
overrides,
327+
).ClientConfig()
328+
}
329+
330+
// GetClientset returns a kubernetes clientset for the provided kubeconfig path and context.
331+
func GetClientset(log logging.Logger, path string, context string) (*kubernetes.Clientset, error) {
332+
clientConfig, err := GetClientConfig(log, path, context)
333+
if err != nil {
334+
return nil, fmt.Errorf("failed to get client config: %w", err)
335+
}
336+
337+
clientset, err := kubernetes.NewForConfig(clientConfig)
338+
if err != nil {
339+
return nil, fmt.Errorf("failed to create clientset: %w", err)
340+
}
341+
return clientset, nil
342+
}
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package tmpnet
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
12+
"go.uber.org/zap"
13+
14+
"github.com/ava-labs/avalanchego/utils/logging"
15+
)
16+
17+
// CheckClusterRunning checks if the configured cluster is accessible.
18+
// TODO(marun) Maybe differentiate between configuration and endpoint errors
19+
func CheckClusterRunning(log logging.Logger, configPath string, configContext string) error {
20+
clientset, err := GetClientset(log, configPath, configContext)
21+
if err != nil {
22+
return err
23+
}
24+
// Check if the configured context can reach a cluster endpoint
25+
_, err = clientset.Discovery().ServerVersion()
26+
return err
27+
}
28+
29+
// StartKindCluster starts a new kind cluster if one is not already running.
30+
func StartKindCluster(ctx context.Context, log logging.Logger, configPath string, configContext string) error {
31+
err := CheckClusterRunning(log, configPath, configContext)
32+
if err == nil {
33+
log.Info("kubernetes cluster already running",
34+
zap.String("kubeconfig", configPath),
35+
zap.String("kubeconfigContext", configContext),
36+
)
37+
return nil
38+
}
39+
40+
log.Debug("kubernetes cluster not running",
41+
zap.String("kubeconfig", configPath),
42+
zap.String("kubeconfigContext", configContext),
43+
zap.Error(err),
44+
)
45+
46+
// Start a new kind cluster
47+
ctx, cancel := context.WithTimeout(ctx, DefaultNetworkTimeout)
48+
defer cancel()
49+
cmd := exec.CommandContext(ctx, "kind-with-registry.sh")
50+
cmd.Stdout = os.Stdout
51+
cmd.Stderr = os.Stderr
52+
if err := cmd.Run(); err != nil {
53+
return fmt.Errorf("failed to run kind-with-registry.sh: %w", err)
54+
}
55+
return nil
56+
}

0 commit comments

Comments
 (0)