Skip to content

Commit d585d49

Browse files
authored
Refactor pkg/kubernetes (#57)
This PR refactos pkg/kubernetes to make it a bit more Goish™, and also tries add some missing unit tests. I think the full files view should be enough, but in any case each commit is trying to be as small and scoped as possible so it might be easier to review that way. * Refactor Kubernetes.GetPod This function wasn't being used, only tested. Here we refactor it so we can eventually encapsulate this logic in this struct, and add proper testing. * Use Kubernetes.GetPods for finding pods * Hide low-level Kubernetes fields These aren't necessary for proper utilization of this struct. * Rename kubernetes.GetKubernetes to kubernetes.New * Refactor kubernetes.New * Remove unused field * Refactor chooseTargetContainer * Add getEphemeralContainerExitCode unit test * Add isEphemeralContainerTerminated unit test * Revert get cluster config logic See [note] on pull request, we should probably fix this. [note]: #57 (comment)
1 parent 9fe6e3d commit d585d49

File tree

3 files changed

+335
-73
lines changed

3 files changed

+335
-73
lines changed

cmd/runner/executetest.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/grafana/nethax/pkg/kubernetes"
1515
"github.com/spf13/cobra"
1616
corev1 "k8s.io/api/core/v1"
17-
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1817
)
1918

2019
// ExecuteTest returns the execute-test command
@@ -44,7 +43,7 @@ func ExecuteTest() *cobra.Command {
4443
common.ExitConfigError()
4544
}
4645

47-
k, err := kubernetes.GetKubernetes("")
46+
k, err := kubernetes.New("")
4847
if err != nil {
4948
cmd.Printf("Error creating Kubernetes client: %v\n", err)
5049
common.ExitConfigError()
@@ -171,19 +170,12 @@ func isPodReady(pod *corev1.Pod) bool {
171170
}
172171

173172
func findPods(ctx context.Context, k *kubernetes.Kubernetes, mode, namespace, selector string) ([]corev1.Pod, error) {
174-
// Find pods matching the selector
175-
pods, err := k.Client.CoreV1().Pods(namespace).List(ctx, v1.ListOptions{
176-
LabelSelector: selector,
177-
})
173+
pods, err := k.GetPods(ctx, namespace, selector)
178174
if err != nil {
179175
return nil, fmt.Errorf("failed to find pods: %w", err)
180176
}
181177

182-
if len(pods.Items) == 0 {
183-
return nil, fmt.Errorf("no pods found matching selector %s", selector)
184-
}
185-
186-
return selectPods(mode, pods.Items)
178+
return selectPods(mode, pods)
187179
}
188180

189181
var (

pkg/kubernetes/kubernetes.go

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package kubernetes
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
7-
"log"
88
"time"
99

1010
corev1 "k8s.io/api/core/v1"
@@ -24,64 +24,76 @@ var (
2424
ProbeImageVersion = "latest"
2525
)
2626

27-
func GetKubernetes(context string) (*Kubernetes, error) {
27+
// New returns a new Kubernetes object, connected to the given
28+
// context, or to the in-cluster API if blank.
29+
func New(context string) (*Kubernetes, error) {
30+
config, err := getClusterConfig(context)
31+
if err != nil {
32+
return nil, fmt.Errorf("fetching Kubernetes configuration: %w", err)
33+
}
34+
35+
client, err := kubernetes.NewForConfig(config)
36+
if err != nil {
37+
return nil, fmt.Errorf("creating Kubernetes client: %w", err)
38+
}
39+
40+
return &Kubernetes{
41+
client: client,
42+
}, nil
43+
}
44+
45+
func getClusterConfig(kontext string) (*rest.Config, error) {
2846
// attempt to use config from pod service account
29-
config, err := rest.InClusterConfig()
47+
cfg, err := rest.InClusterConfig()
3048
if err != nil {
3149
// Can be overridden by KUBECONFIG variable
3250
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
33-
configOverride := &clientcmd.ConfigOverrides{}
34-
if context != "" {
35-
configOverride.CurrentContext = context
51+
configOverride := &clientcmd.ConfigOverrides{
52+
CurrentContext: kontext,
3653
}
3754

38-
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
55+
cfg, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
3956
loadingRules,
4057
configOverride,
4158
).ClientConfig()
42-
4359
if err != nil {
44-
return nil, fmt.Errorf("fetching Kubernetes configuration: %w", err)
60+
return nil, fmt.Errorf("loading Kubernetes configuration: %w", err)
4561
}
4662
}
4763

48-
client, err := kubernetes.NewForConfig(config)
49-
if err != nil {
50-
return nil, fmt.Errorf("creating Kubernetes client: %w", err)
51-
}
52-
53-
return &Kubernetes{
54-
Client: client,
55-
Config: config,
56-
}, nil
64+
return cfg, nil
5765
}
5866

5967
type Kubernetes struct {
60-
Config *rest.Config
61-
Client kubernetes.Interface
68+
client kubernetes.Interface
6269
}
6370

64-
func (k *Kubernetes) GetPods(ctx context.Context, namespace string) []string {
65-
pods, err := k.Client.CoreV1().Pods(namespace).List(
66-
ctx,
67-
metav1.ListOptions{})
71+
var (
72+
errNoPodsFound = errors.New("no pods found")
73+
errNoContainersInPod = errors.New("no containers in pod")
74+
)
6875

76+
func (k *Kubernetes) GetPods(ctx context.Context, namespace, selector string) ([]corev1.Pod, error) {
77+
pods, err := k.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
78+
LabelSelector: selector,
79+
})
6980
if err != nil {
70-
panic(err.Error())
81+
return nil, fmt.Errorf("listing pods for namespace %s and selector %q: %w", namespace, selector, err)
7182
}
72-
podNames := []string{}
73-
for _, pod := range pods.Items {
74-
podNames = append(podNames, pod.Name)
83+
84+
if len(pods.Items) == 0 {
85+
return nil, fmt.Errorf("%w: namespace %s, selector %q", errNoPodsFound, namespace, selector)
7586
}
76-
return podNames
87+
88+
return pods.Items, nil
7789
}
7890

79-
func chooseTargetContainer(pod *corev1.Pod) string {
91+
func chooseTargetContainer(pod *corev1.Pod) (string, error) {
8092
// TODO add capability to pick container by name (currently assume 0th container)
8193
if len(pod.Spec.Containers) == 0 {
82-
log.Fatalf("Error: No containers in pod.")
94+
return "", errNoContainersInPod
8395
}
84-
return pod.Spec.Containers[0].Name
96+
return pod.Spec.Containers[0].Name, nil
8597
}
8698

8799
func (k *Kubernetes) LaunchEphemeralContainer(ctx context.Context, pod *corev1.Pod, command []string, args []string) (*corev1.Pod, string, error) {
@@ -92,14 +104,19 @@ func (k *Kubernetes) LaunchEphemeralContainer(ctx context.Context, pod *corev1.P
92104

93105
ephemeralName := fmt.Sprintf("nethax-probe-%v", time.Now().UnixNano())
94106

107+
targetContainer, err := chooseTargetContainer(pod)
108+
if err != nil {
109+
return nil, "", fmt.Errorf("choosing target container: %w", err)
110+
}
111+
95112
debugContainer := &corev1.EphemeralContainer{
96113
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
97114
Name: ephemeralName,
98115
Image: fmt.Sprintf("nethax-probe:%s", ProbeImageVersion),
99116
Command: command,
100117
Args: args,
101118
},
102-
TargetContainerName: chooseTargetContainer(pod),
119+
TargetContainerName: targetContainer,
103120
}
104121

105122
debugPod := pod.DeepCopy()
@@ -115,7 +132,7 @@ func (k *Kubernetes) LaunchEphemeralContainer(ctx context.Context, pod *corev1.P
115132
return nil, ephemeralName, fmt.Errorf("error creating patch to add debug container: %v", err)
116133
}
117134

118-
pods := k.Client.CoreV1().Pods(pod.Namespace)
135+
pods := k.client.CoreV1().Pods(pod.Namespace)
119136
result, err := pods.Patch(ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "ephemeralcontainers")
120137
if err != nil {
121138
return nil, ephemeralName, fmt.Errorf("error patching pod with debug container: %v", err)
@@ -125,7 +142,7 @@ func (k *Kubernetes) LaunchEphemeralContainer(ctx context.Context, pod *corev1.P
125142
}
126143

127144
func (k *Kubernetes) getEphemeralContainerExitStatus(ctx context.Context, pod *corev1.Pod, ephemeralContainerName string) (int32, error) {
128-
pod, err := k.Client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
145+
pod, err := k.client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
129146
if err != nil {
130147
return -1, err
131148
}

0 commit comments

Comments
 (0)