/
shell.go
163 lines (130 loc) · 4.58 KB
/
shell.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
package cmd
import (
"encoding/json"
"os"
"os/exec"
"os/user"
"strconv"
"strings"
"syscall"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type shellPodOverrides struct {
APIVersion string `json:"apiVersion"`
Metadata shellPodMetadata `json:"metadata"`
Spec shellPodSpec `json:"spec"`
}
type shellPodMetadata struct {
Annotations map[string]string `json:"annotations"`
GenerateName string `json:"generateName"`
Name string `json:"name"`
}
type shellPodSpec struct {
ServiceAccountName string `json:"serviceAccountName"`
}
// shellCmd represents the shell command
var shellCmd = &cobra.Command{
Use: "shell",
Short: "Launch a shell container in a Truss cluster",
Long: `Launches a shell Pod in a Truss cluster. Useful for debugging purposes.
Examples:
# Run a shell in the current namespace with the default image
truss shell -e nonprod-cmh
# Run a shell in the 'vault' Namespace with the 'vault' ServiceAccount
truss shell -e nonprod-cmh -n vault -s vault
# Run a shell in the 'cdp-edge' Namespace with Istio disabled
truss shell -e nonprod-cmh -n cdp-edge --istio=false
# Run a Pod with a different image and an alternative command
truss shell -e nonprod-cmh -i nicolaka/netshoot
# Run a container with a non-default command
truss shell -e nonprod-cmh -- ls -al /
`,
RunE: func(cmd *cobra.Command, args []string) error {
// Set KUBECONFIG and verify availability of kubectl
kubeconfig, err := getKubeconfig()
if err != nil {
return err
}
err = os.Setenv("KUBECONFIG", kubeconfig)
if err != nil {
return err
}
kubectlBinary, err := exec.LookPath("kubectl")
if err != nil {
return errors.Wrap(err, "Unable to locate kubectl binary")
}
// Generate a user-specific name template for the pod
user, err := user.Current()
if err != nil {
return nil
}
podName := "shell-" + strings.Replace(user.Username, ".", "-", -1)
// Fetch options from flags
image, err := cmd.Flags().GetString("image")
if err != nil {
return err
}
istioEnabled, err := cmd.Flags().GetBool("istio")
if err != nil {
return err
}
namespace, err := cmd.Flags().GetString("namespace")
if err != nil {
return err
}
serviceaccount, err := cmd.Flags().GetString("serviceaccount")
if err != nil {
return err
}
// Build and exec the kubectl command
kubectlArgs, err := buildShellKubectlArgs(podName, image, istioEnabled, serviceaccount, namespace, args)
if err != nil {
return err
}
return syscall.Exec(kubectlBinary, kubectlArgs, os.Environ())
},
}
func buildShellKubectlArgs(podName string, image string, istioEnabled bool, serviceaccount string, namespace string, cmdArgs []string) ([]string, error) {
kubectlArgs := []string{"kubectl", "run", podName, "--image-pull-policy=Always", "--restart=Never", "--rm", "--stdin", "--tty", "--attach=true", "--image=" + image}
// Adding a label so we can easily find and clean these up later
kubectlArgs = append(kubectlArgs, "--labels=truss.bridgeops.sh/shell=true")
if istioEnabled {
kubectlArgs = append(kubectlArgs, "--env=ISTIO_ENABLED=true")
}
// Here we are overriding the following in the Pod spec:
// * name/generateName to give the pod a unique name
// * annotations to enable/disable Istio injection
overrides := &shellPodOverrides{}
overrides.APIVersion = "v1"
overrides.Metadata.Name = ""
overrides.Metadata.GenerateName = podName + "-"
overrides.Metadata.Annotations = map[string]string{
"sidecar.istio.io/inject": strconv.FormatBool(istioEnabled),
}
if serviceaccount != "" {
overrides.Spec.ServiceAccountName = serviceaccount
}
overridesJSON, err := json.Marshal(overrides)
if err != nil {
return kubectlArgs, err
}
if namespace != "" {
kubectlArgs = append(kubectlArgs, "--namespace="+namespace)
}
kubectlArgs = append(kubectlArgs, "--overrides="+string(overridesJSON))
// Normally , we'll run the image with its default command, but this allows
// it to be overriden.
if len(cmdArgs) > 0 {
kubectlArgs = append(kubectlArgs, "--")
kubectlArgs = append(kubectlArgs, cmdArgs...)
}
return kubectlArgs, nil
}
func init() {
shellCmd.Flags().StringP("image", "i", "jdharrington/toolbox:latest", "The Docker image to use")
shellCmd.Flags().StringP("namespace", "n", "", "Namespace to run the shell Pod in")
shellCmd.Flags().StringP("serviceaccount", "s", "", "ServiceAccount to run the shell Pod as")
shellCmd.Flags().BoolP("istio", "", false, "Whether or not the shell Pod should be Istio-enabled. Only has effect when launching a shell Pod in an Istio-enabled Namespace")
rootCmd.AddCommand(shellCmd)
}