blob: 91237ba821889ae2a4028f06d2fcef92ee25c516 [file] [log] [blame]
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +02001// Package raw wraps SSH commands necessary for K8s inspection.
2package raw
3
4import (
5 "bytes"
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +02006 "fmt"
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +02007 "io/ioutil"
8 "os/user"
9 "path/filepath"
10
11 "golang.org/x/crypto/ssh"
12 kh "golang.org/x/crypto/ssh/knownhosts"
13
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020014 "check"
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020015 "check/config"
16)
17
18const (
19 controlplane = "controlplane"
20 etcd = "etcd"
21 worker = "worker"
22
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020023 knownHostsFile = "~/.ssh/known_hosts"
24)
25
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020026// Raw implements Informer interface.
27type Raw struct {
28 check.Informer
29}
30
31// GetAPIParams returns parameters of running Kubernetes API servers.
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020032// It queries only cluster nodes with "controlplane" role.
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020033func (r *Raw) GetAPIParams() ([]string, error) {
34 return getProcessParams(check.APIProcess)
35}
36
Pawel Wieczorek96f4e2f2019-09-27 16:10:33 +020037// GetSchedulerParams returns parameters of running Kubernetes scheduler.
38// It queries only cluster nodes with "controlplane" role.
39func (r *Raw) GetSchedulerParams() ([]string, error) {
40 return getProcessParams(check.SchedulerProcess)
41}
42
Pawel Wieczorek5a61d612019-09-27 18:26:13 +020043// GetControllerManagerParams returns parameters of running Kubernetes scheduler.
44// It queries only cluster nodes with "controlplane" role.
45func (r *Raw) GetControllerManagerParams() ([]string, error) {
46 return getProcessParams(check.ControllerManagerProcess)
47}
48
Pawel Wieczorekf649f222019-10-07 17:00:49 +020049// GetEtcdParams returns parameters of running etcd.
50// It queries only cluster nodes with "controlplane" role.
51func (r *Raw) GetEtcdParams() ([]string, error) {
Pawel Wieczoreke15544d2019-10-08 14:43:47 +020052 return getProcessParams(check.EtcdProcess)
Pawel Wieczorekf649f222019-10-07 17:00:49 +020053}
54
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020055func getProcessParams(process check.Command) ([]string, error) {
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020056 nodes, err := config.GetNodesInfo()
57 if err != nil {
58 return []string{}, err
59 }
60
61 for _, node := range nodes {
62 if isControlplaneNode(node.Role) {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020063 cmd, err := getInspectCmdOutput(node, process)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020064 if err != nil {
65 return []string{}, err
66 }
67
Pawel Wieczorek752f7fe2019-09-30 14:39:32 +020068 cmd = trimOutput(cmd) // TODO: improve `docker inspect` query format.
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020069 if len(cmd) > 0 {
Pawel Wieczoreke15544d2019-10-08 14:43:47 +020070 if process == check.EtcdProcess { // etcd process name is not included in its argument list.
71 return btos(cmd), nil
72 }
73
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020074 i := bytes.Index(cmd, []byte(process.String()))
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020075 if i == -1 {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020076 return []string{}, fmt.Errorf("missing %s command", process)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020077 }
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020078 return btos(cmd[i+len(process.String()):]), nil
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020079 }
80 }
81 }
82
83 return []string{}, nil
84}
85
86func isControlplaneNode(roles []string) bool {
87 for _, role := range roles {
88 if role == controlplane {
89 return true
90 }
91 }
92 return false
93}
94
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020095func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020096 path, err := expandPath(node.SSHKeyPath)
97 if err != nil {
98 return nil, err
99 }
100
101 pubKey, err := parsePublicKey(path)
102 if err != nil {
103 return nil, err
104 }
105
106 khPath, err := expandPath(knownHostsFile)
107 if err != nil {
108 return nil, err
109 }
110
111 hostKeyCallback, err := kh.New(khPath)
112 if err != nil {
113 return nil, err
114 }
115
116 config := &ssh.ClientConfig{
117 User: node.User,
118 Auth: []ssh.AuthMethod{pubKey},
119 HostKeyCallback: hostKeyCallback,
120 }
121
122 conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
123 if err != nil {
124 return nil, err
125 }
126 defer conn.Close()
127
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +0200128 out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200129 if err != nil {
130 return nil, err
131 }
132 return out, nil
133}
134
135func expandPath(path string) (string, error) {
136 if len(path) == 0 || path[0] != '~' {
137 return path, nil
138 }
139
140 usr, err := user.Current()
141 if err != nil {
142 return "", err
143 }
144 return filepath.Join(usr.HomeDir, path[1:]), nil
145}
146
147func parsePublicKey(path string) (ssh.AuthMethod, error) {
148 key, err := ioutil.ReadFile(path)
149 if err != nil {
150 return nil, err
151 }
152 signer, err := ssh.ParsePrivateKey(key)
153 if err != nil {
154 return nil, err
155 }
156 return ssh.PublicKeys(signer), nil
157}
158
159func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
160 sess, err := conn.NewSession()
161 if err != nil {
162 return nil, err
163 }
164 defer sess.Close()
165 out, err := sess.Output(cmd)
166 if err != nil {
167 return nil, err
168 }
169 return out, nil
170}
171
Pawel Wieczorek752f7fe2019-09-30 14:39:32 +0200172// trimOutput removes trailing new line and brackets from output.
173func trimOutput(b []byte) []byte {
174 b = bytes.TrimSpace(b)
175 b = bytes.TrimPrefix(b, []byte("["))
176 b = bytes.TrimSuffix(b, []byte("]"))
177 return b
178}
179
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200180// btos converts slice of bytes to slice of strings split by white space characters.
181func btos(in []byte) []string {
182 var out []string
183 for _, b := range bytes.Fields(in) {
184 out = append(out, string(b))
185 }
186 return out
187}