blob: 555115950eec85e54925f5428762803b83b5a253 [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 Wieczorek76dd9bf2019-09-26 16:43:01 +020049func getProcessParams(process check.Command) ([]string, error) {
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020050 nodes, err := config.GetNodesInfo()
51 if err != nil {
52 return []string{}, err
53 }
54
55 for _, node := range nodes {
56 if isControlplaneNode(node.Role) {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020057 cmd, err := getInspectCmdOutput(node, process)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020058 if err != nil {
59 return []string{}, err
60 }
61
Pawel Wieczorek752f7fe2019-09-30 14:39:32 +020062 cmd = trimOutput(cmd) // TODO: improve `docker inspect` query format.
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020063 if len(cmd) > 0 {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020064 i := bytes.Index(cmd, []byte(process.String()))
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020065 if i == -1 {
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020066 return []string{}, fmt.Errorf("missing %s command", process)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020067 }
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020068 return btos(cmd[i+len(process.String()):]), nil
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020069 }
70 }
71 }
72
73 return []string{}, nil
74}
75
76func isControlplaneNode(roles []string) bool {
77 for _, role := range roles {
78 if role == controlplane {
79 return true
80 }
81 }
82 return false
83}
84
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +020085func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +020086 path, err := expandPath(node.SSHKeyPath)
87 if err != nil {
88 return nil, err
89 }
90
91 pubKey, err := parsePublicKey(path)
92 if err != nil {
93 return nil, err
94 }
95
96 khPath, err := expandPath(knownHostsFile)
97 if err != nil {
98 return nil, err
99 }
100
101 hostKeyCallback, err := kh.New(khPath)
102 if err != nil {
103 return nil, err
104 }
105
106 config := &ssh.ClientConfig{
107 User: node.User,
108 Auth: []ssh.AuthMethod{pubKey},
109 HostKeyCallback: hostKeyCallback,
110 }
111
112 conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
113 if err != nil {
114 return nil, err
115 }
116 defer conn.Close()
117
Pawel Wieczorek76dd9bf2019-09-26 16:43:01 +0200118 out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200119 if err != nil {
120 return nil, err
121 }
122 return out, nil
123}
124
125func expandPath(path string) (string, error) {
126 if len(path) == 0 || path[0] != '~' {
127 return path, nil
128 }
129
130 usr, err := user.Current()
131 if err != nil {
132 return "", err
133 }
134 return filepath.Join(usr.HomeDir, path[1:]), nil
135}
136
137func parsePublicKey(path string) (ssh.AuthMethod, error) {
138 key, err := ioutil.ReadFile(path)
139 if err != nil {
140 return nil, err
141 }
142 signer, err := ssh.ParsePrivateKey(key)
143 if err != nil {
144 return nil, err
145 }
146 return ssh.PublicKeys(signer), nil
147}
148
149func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
150 sess, err := conn.NewSession()
151 if err != nil {
152 return nil, err
153 }
154 defer sess.Close()
155 out, err := sess.Output(cmd)
156 if err != nil {
157 return nil, err
158 }
159 return out, nil
160}
161
Pawel Wieczorek752f7fe2019-09-30 14:39:32 +0200162// trimOutput removes trailing new line and brackets from output.
163func trimOutput(b []byte) []byte {
164 b = bytes.TrimSpace(b)
165 b = bytes.TrimPrefix(b, []byte("["))
166 b = bytes.TrimSuffix(b, []byte("]"))
167 return b
168}
169
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +0200170// btos converts slice of bytes to slice of strings split by white space characters.
171func btos(in []byte) []string {
172 var out []string
173 for _, b := range bytes.Fields(in) {
174 out = append(out, string(b))
175 }
176 return out
177}