blob: eea5c01d22db396667b3c55a302c0808287d4fef [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) {
52 return []string{}, check.ErrNotImplemented
53}
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 Wieczorek76dd9bf2019-09-26 16:43:01 +020070 i := bytes.Index(cmd, []byte(process.String()))
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020071 if i == -1 {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020072 return []string{}, fmt.Errorf("missing %s command", process)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020073 }
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020074 return btos(cmd[i+len(process.String()):]), nil
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020075 }
76 }
77 }
78
79 return []string{}, nil
80}
81
82func isControlplaneNode(roles []string) bool {
83 for _, role := range roles {
84 if role == controlplane {
85 return true
86 }
87 }
88 return false
89}
90
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020091func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020092 path, err := expandPath(node.SSHKeyPath)
93 if err != nil {
94 return nil, err
95 }
96
97 pubKey, err := parsePublicKey(path)
98 if err != nil {
99 return nil, err
100 }
101
102 khPath, err := expandPath(knownHostsFile)
103 if err != nil {
104 return nil, err
105 }
106
107 hostKeyCallback, err := kh.New(khPath)
108 if err != nil {
109 return nil, err
110 }
111
112 config := &ssh.ClientConfig{
113 User: node.User,
114 Auth: []ssh.AuthMethod{pubKey},
115 HostKeyCallback: hostKeyCallback,
116 }
117
118 conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
119 if err != nil {
120 return nil, err
121 }
122 defer conn.Close()
123
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +0200124 out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200125 if err != nil {
126 return nil, err
127 }
128 return out, nil
129}
130
131func expandPath(path string) (string, error) {
132 if len(path) == 0 || path[0] != '~' {
133 return path, nil
134 }
135
136 usr, err := user.Current()
137 if err != nil {
138 return "", err
139 }
140 return filepath.Join(usr.HomeDir, path[1:]), nil
141}
142
143func parsePublicKey(path string) (ssh.AuthMethod, error) {
144 key, err := ioutil.ReadFile(path)
145 if err != nil {
146 return nil, err
147 }
148 signer, err := ssh.ParsePrivateKey(key)
149 if err != nil {
150 return nil, err
151 }
152 return ssh.PublicKeys(signer), nil
153}
154
155func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
156 sess, err := conn.NewSession()
157 if err != nil {
158 return nil, err
159 }
160 defer sess.Close()
161 out, err := sess.Output(cmd)
162 if err != nil {
163 return nil, err
164 }
165 return out, nil
166}
167
Pawel Wieczorek752f7fe2019-09-30 14:39:32 +0200168// trimOutput removes trailing new line and brackets from output.
169func trimOutput(b []byte) []byte {
170 b = bytes.TrimSpace(b)
171 b = bytes.TrimPrefix(b, []byte("["))
172 b = bytes.TrimSuffix(b, []byte("]"))
173 return b
174}
175
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200176// btos converts slice of bytes to slice of strings split by white space characters.
177func btos(in []byte) []string {
178 var out []string
179 for _, b := range bytes.Fields(in) {
180 out = append(out, string(b))
181 }
182 return out
183}