forked from helm/chart-testing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kubectl.go
214 lines (179 loc) · 6.92 KB
/
kubectl.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
package tool
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/helm/chart-testing/pkg/exec"
"github.com/pkg/errors"
)
type Kubectl struct {
exec exec.ProcessExecutor
}
func NewKubectl(exec exec.ProcessExecutor) Kubectl {
return Kubectl{
exec: exec,
}
}
// DeleteNamespace deletes the specified namespace. If the namespace does not terminate within 120s, pods running in the
// namespace and, eventually, the namespace itself are force-deleted.
func (k Kubectl) DeleteNamespace(namespace string) {
fmt.Printf("Deleting namespace '%s'...\n", namespace)
timeoutSec := "180s"
if err := k.exec.RunProcess("kubectl", "delete", "namespace", namespace, "--timeout", timeoutSec); err != nil {
fmt.Printf("Namespace '%s' did not terminate after %s.\n", namespace, timeoutSec)
}
if k.getNamespace(namespace) {
fmt.Printf("Namespace '%s' did not terminate after %s.\n", namespace, timeoutSec)
fmt.Println("Force-deleting everything...")
if err := k.exec.RunProcess("kubectl", "delete", "all", "--namespace", namespace, "--all", "--force", "--grace-period=0"); err != nil {
fmt.Printf("Error deleting everything in the namespace %v: %v", namespace, err)
}
// Give it some more time to be deleted by K8s
time.Sleep(5 * time.Second)
if k.getNamespace(namespace) {
if err := k.forceNamespaceDeletion(namespace); err != nil {
fmt.Println("Error force deleting namespace:", err)
}
}
}
}
func (k Kubectl) forceNamespaceDeletion(namespace string) error {
// Getting the namespace json to remove the finalizer
cmdOutput, err := k.exec.RunProcessAndCaptureOutput("kubectl", "get", "namespace", namespace, "--output=json")
if err != nil {
fmt.Println("Error getting namespace json:", err)
return err
}
namespaceUpdate := map[string]interface{}{}
err = json.Unmarshal([]byte(cmdOutput), &namespaceUpdate)
if err != nil {
fmt.Println("Error in unmarshalling the payload:", err)
return err
}
namespaceUpdate["spec"] = nil
namespaceUpdateBytes, err := json.Marshal(&namespaceUpdate)
if err != nil {
fmt.Println("Error in marshalling the payload:", err)
return err
}
// Remove finalizer from the namespace
fun := func(port int) error {
fmt.Printf("Removing finalizers from namespace '%s'...\n", namespace)
k8sURL := fmt.Sprintf("http://127.0.0.1:%d/api/v1/namespaces/%s/finalize", port, namespace)
req, err := retryablehttp.NewRequest("PUT", k8sURL, bytes.NewReader(namespaceUpdateBytes))
if err != nil {
fmt.Println("Error creating the request to update the namespace:", err)
return err
}
req.Header.Set("Content-Type", "application/json")
errMsg := "Error removing finalizer from namespace"
client := retryablehttp.NewClient()
client.Logger = nil
if resp, err := client.Do(req); err != nil {
return errors.Wrap(err, errMsg)
} else if resp.StatusCode != http.StatusOK {
return errors.New(errMsg)
}
return nil
}
err = k.exec.RunWithProxy(fun)
if err != nil {
return errors.Wrapf(err, "Cannot force-delete namespace '%s'", namespace)
}
// Give it some more time to be deleted by K8s
time.Sleep(5 * time.Second)
// Check again
if _, err := k.exec.RunProcessAndCaptureOutput("kubectl", "get", "namespace", namespace); err != nil {
fmt.Printf("Namespace '%s' terminated.\n", namespace)
return nil
}
fmt.Printf("Force-deleting namespace '%s'...\n", namespace)
if err := k.exec.RunProcess("kubectl", "delete", "namespace", namespace, "--force", "--grace-period=0", "--ignore-not-found=true"); err != nil {
fmt.Println("Error deleting namespace:", err)
return err
}
return nil
}
func (k Kubectl) WaitForDeployments(namespace string, selector string) error {
output, err := k.exec.RunProcessAndCaptureOutput(
"kubectl", "get", "deployments", "--namespace", namespace, "--selector", selector, "--output", "jsonpath={.items[*].metadata.name}")
if err != nil {
return err
}
deployments := strings.Fields(output)
for _, deployment := range deployments {
deployment = strings.Trim(deployment, "'")
err := k.exec.RunProcess("kubectl", "rollout", "status", "deployment", deployment, "--namespace", namespace)
if err != nil {
return err
}
// 'kubectl rollout status' does not return a non-zero exit code when rollouts fail.
// We, thus, need to double-check here.
//
// Just after rollout, pods from the previous deployment revision may still be in a
// terminating state.
unavailable, err := k.exec.RunProcessAndCaptureOutput("kubectl", "get", "deployment", deployment, "--namespace", namespace, "--output",
`jsonpath={.status.unavailableReplicas}`)
if err != nil {
return err
}
if unavailable != "" && unavailable != "0" {
return fmt.Errorf("%s replicas unavailable", unavailable)
}
}
return nil
}
func (k Kubectl) GetPodsforDeployment(namespace string, deployment string) ([]string, error) {
jsonString, _ := k.exec.RunProcessAndCaptureOutput("kubectl", "get", "deployment", deployment, "--namespace", namespace, "--output=json")
var deploymentMap map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &deploymentMap)
if err != nil {
return nil, err
}
spec := deploymentMap["spec"].(map[string]interface{})
selector := spec["selector"].(map[string]interface{})
matchLabels := selector["matchLabels"].(map[string]interface{})
var ls string
for name, value := range matchLabels {
if ls != "" {
ls += ","
}
ls += fmt.Sprintf("%s=%s", name, value)
}
return k.GetPods("--selector", ls, "--namespace", namespace, "--output", "jsonpath={.items[*].metadata.name}")
}
func (k Kubectl) GetPods(args ...string) ([]string, error) {
kubectlArgs := []string{"get", "pods"}
kubectlArgs = append(kubectlArgs, args...)
pods, err := k.exec.RunProcessAndCaptureOutput("kubectl", kubectlArgs)
if err != nil {
return nil, err
}
return strings.Fields(pods), nil
}
func (k Kubectl) GetEvents(namespace string) error {
return k.exec.RunProcess("kubectl", "get", "events", "--output", "wide", "--namespace", namespace)
}
func (k Kubectl) DescribePod(namespace string, pod string) error {
return k.exec.RunProcess("kubectl", "describe", "pod", pod, "--namespace", namespace)
}
func (k Kubectl) Logs(namespace string, pod string, container string) error {
return k.exec.RunProcess("kubectl", "logs", pod, "--namespace", namespace, "--container", container)
}
func (k Kubectl) GetInitContainers(namespace string, pod string) ([]string, error) {
return k.GetPods(pod, "--no-headers", "--namespace", namespace, "--output", "jsonpath={.spec.initContainers[*].name}")
}
func (k Kubectl) GetContainers(namespace string, pod string) ([]string, error) {
return k.GetPods(pod, "--no-headers", "--namespace", namespace, "--output", "jsonpath={.spec.containers[*].name}")
}
func (k Kubectl) getNamespace(namespace string) bool {
if _, err := k.exec.RunProcessAndCaptureOutput("kubectl", "get", "namespace", namespace); err != nil {
fmt.Printf("Namespace '%s' terminated.\n", namespace)
return false
}
return true
}