ELIOT Command Line Interface Commit
[eliot.git] / blueprints / common / elcli / elcli / cmd / util / ubuntuinstaller.go
1 /*
2 Copyright 2019 The Kubeedge Authors.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 package util
18
19 import (
20         "fmt"
21         //"os"
22         "os/exec"
23         "strings"
24
25         //types "github.com/kubeedge/kubeedge/keadm/app/cmd/common"
26         types "elcli/cmd/common"
27 )
28
29 const downloadRetryTimes int = 3
30
31 // Ubuntu releases
32 const (
33         UbuntuXenial = "xenial"
34         UbuntuBionic = "bionic"
35 )
36
37 //UbuntuOS struct objects shall have information of the tools version to be installed
38 //on Hosts having Ubuntu OS.
39 //It implements OSTypeInstaller interface
40 type UbuntuOS struct {
41         DockerVersion     string
42         KubernetesVersion string
43         KubeEdgeVersion   string
44         IsEdgeNode        bool //True - Edgenode False - EliotCloudnode
45         K8SImageRepository string
46         K8SPodNetworkCidr  string
47 }
48
49 //SetDockerVersion sets the Docker version for the objects instance
50 func (u *UbuntuOS) SetDockerVersion(version string) {
51         u.DockerVersion = version
52 }
53
54 //SetK8SVersionAndIsNodeFlag sets the K8S version for the objects instance
55 //It also sets if this host shall act as edge node or not
56 func (u *UbuntuOS) SetK8SVersionAndIsNodeFlag(version string, flag bool) {
57         u.KubernetesVersion = version
58         u.IsEdgeNode = flag
59 }
60
61 //SetK8SImageRepoAndPodNetworkCidr sets the K8S image Repository and pod network
62 // cidr.
63 func (u *UbuntuOS) SetK8SImageRepoAndPodNetworkCidr(repo, cidr string) {
64         u.K8SImageRepository = repo
65         u.K8SPodNetworkCidr = cidr
66 }
67
68 //SetKubeEdgeVersion sets the KubeEdge version for the objects instance
69 func (u *UbuntuOS) SetKubeEdgeVersion(version string) {
70         u.KubeEdgeVersion = version
71 }
72
73 //IsToolVerInRepo checks if the tool mentioned in available in OS repo or not
74 func (u *UbuntuOS) IsToolVerInRepo(toolName, version string) (bool, error) {
75         //Check if requested Docker or K8S components said version is available in OS repo or not
76
77         chkToolVer := fmt.Sprintf("apt-cache madison '%s' | grep -w %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", toolName, version)
78         cmd := &Command{Cmd: exec.Command("sh", "-c", chkToolVer)}
79         cmd.ExecuteCommand()
80         stdout := cmd.GetStdOutput()
81         errout := cmd.GetStdErr()
82
83         if errout != "" {
84                 return false, fmt.Errorf("%s", errout)
85         }
86
87         if stdout != "" {
88                 fmt.Println(toolName, stdout, "is available in OS repo")
89                 return true, nil
90         }
91
92         fmt.Println(toolName, "version", version, "not found in OS repo")
93         return false, nil
94 }
95
96 func (u *UbuntuOS) addDockerRepositoryAndUpdate() error {
97         //lsb_release -cs
98         cmd := &Command{Cmd: exec.Command("sh", "-c", "lsb_release -cs")}
99         cmd.ExecuteCommand()
100         distVersion := cmd.GetStdOutput()
101         if distVersion == "" {
102                 return fmt.Errorf("ubuntu dist version not available")
103         }
104         fmt.Println("Ubuntu distribution version is", distVersion)
105
106         //'apt-get update'
107         stdout, err := runCommandWithShell("apt-get update")
108         if err != nil {
109                 return err
110         }
111         fmt.Println(stdout)
112
113         //"curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | apt-key add"
114         //Get the GPG key
115         curl := fmt.Sprintf("curl -fsSL \"%s/linux/%s/gpg\" | apt-key add", DefaultDownloadURL, UbuntuOSType)
116         cmd = &Command{Cmd: exec.Command("sh", "-c", curl)}
117         cmd.ExecuteCommand()
118         curlOutput := cmd.GetStdOutput()
119         if curlOutput == "" {
120                 return fmt.Errorf("not able add the apt key")
121         }
122         fmt.Println(curlOutput)
123
124         //Add the repo in OS source.list
125         aptRepo := fmt.Sprintf("deb [arch=$(dpkg --print-architecture)] %s/linux/%s %s stable", DefaultDownloadURL, UbuntuOSType, distVersion)
126         updtRepo := fmt.Sprintf("echo \"%s\" > /etc/apt/sources.list.d/docker.list", aptRepo)
127         cmd = &Command{Cmd: exec.Command("sh", "-c", updtRepo)}
128         cmd.ExecuteCommand()
129         updtRepoErr := cmd.GetStdErr()
130         if updtRepoErr != "" {
131                 return fmt.Errorf("not able add update repo due to error : %s", updtRepoErr)
132         }
133
134         //Do an apt-get update
135         stdout, err = runCommandWithShell("apt-get update")
136         if err != nil {
137                 return err
138         }
139         fmt.Println(stdout)
140
141         return nil
142 }
143
144 //IsDockerInstalled checks if docker is installed in the host or not
145 func (u *UbuntuOS) IsDockerInstalled(defVersion string) (types.InstallState, error) {
146         cmd := &Command{Cmd: exec.Command("sh", "-c", "docker -v | cut -d ' ' -f3 | cut -d ',' -f1")}
147         cmd.ExecuteCommand()
148         str := cmd.GetStdOutput()
149
150         if strings.Contains(str, u.DockerVersion) {
151                 return types.AlreadySameVersionExist, nil
152         }
153
154         if err := u.addDockerRepositoryAndUpdate(); err != nil {
155                 return types.VersionNAInRepo, err
156         }
157
158         if str == "" {
159                 return types.NewInstallRequired, nil
160         }
161
162         isReqVerAvail, err := u.IsToolVerInRepo("docker-ce", u.DockerVersion)
163         if err != nil {
164                 return types.VersionNAInRepo, err
165         }
166
167         var isDefVerAvail bool
168         if u.DockerVersion != defVersion {
169                 isDefVerAvail, err = u.IsToolVerInRepo("docker-ce", defVersion)
170                 if err != nil {
171                         return types.VersionNAInRepo, err
172                 }
173         }
174
175         if isReqVerAvail {
176                 return types.NewInstallRequired, nil
177         }
178
179         if isDefVerAvail {
180                 return types.DefVerInstallRequired, nil
181         }
182
183         return types.VersionNAInRepo, nil
184 }
185
186 //InstallDocker will install the specified docker in the host
187 func (u *UbuntuOS) InstallDocker() error {
188         fmt.Println("Installing ", u.DockerVersion, "version of docker")
189
190         //Do an apt-get install
191         instPreReq := fmt.Sprintf("apt-get install -y %s", DockerPreqReqList)
192         stdout, err := runCommandWithShell(instPreReq)
193         if err != nil {
194                 return err
195         }
196         fmt.Println(stdout)
197
198         //Get the exact version string from OS repo, so that it can search and install.
199         chkDockerVer := fmt.Sprintf("apt-cache madison 'docker-ce' | grep %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", u.DockerVersion)
200         cmd := &Command{Cmd: exec.Command("sh", "-c", chkDockerVer)}
201         cmd.ExecuteCommand()
202         stdout = cmd.GetStdOutput()
203         errout := cmd.GetStdErr()
204         if errout != "" {
205                 return fmt.Errorf("%s", errout)
206         }
207
208         fmt.Println("Expected docker version to install is", stdout)
209
210         //Install docker-ce
211         dockerInst := fmt.Sprintf("apt-get install -y --allow-change-held-packages --allow-downgrades docker-ce=%s", stdout)
212         stdout, err = runCommandWithShell(dockerInst)
213         if err != nil {
214                 return err
215         }
216         fmt.Println(stdout)
217
218         fmt.Println("Docker", u.DockerVersion, "version is installed in this Host")
219
220         return nil
221 }
222
223 //IsK8SComponentInstalled checks if said K8S version is already installed in the host
224 func (u *UbuntuOS) IsK8SComponentInstalled(component, defVersion string) (types.InstallState, error) {
225
226         find := fmt.Sprintf("dpkg -l | grep %s | awk '{print $3}'", component)
227         cmd := &Command{Cmd: exec.Command("sh", "-c", find)}
228         cmd.ExecuteCommand()
229         str := cmd.GetStdOutput()
230
231         if strings.Contains(str, u.KubernetesVersion) {
232                 return types.AlreadySameVersionExist, nil
233         }
234
235         if err := u.addK8SRepositoryAndUpdate(); err != nil {
236                 return types.VersionNAInRepo, err
237         }
238
239         if str == "" {
240                 return types.NewInstallRequired, nil
241         }
242
243         isReqVerAvail, err := u.IsToolVerInRepo(component, u.KubernetesVersion)
244         if err != nil {
245                 return types.VersionNAInRepo, err
246         }
247
248         var isDefVerAvail bool
249         if u.KubernetesVersion != defVersion {
250                 isDefVerAvail, _ = u.IsToolVerInRepo(component, defVersion)
251                 if err != nil {
252                         return types.VersionNAInRepo, err
253                 }
254         }
255
256         if isReqVerAvail {
257                 return types.NewInstallRequired, nil
258         }
259
260         if isDefVerAvail {
261                 return types.DefVerInstallRequired, nil
262         }
263
264         return types.VersionNAInRepo, nil
265 }
266
267 func (u *UbuntuOS) addK8SRepositoryAndUpdate() error {
268         //Get the distribution version
269         cmd := &Command{Cmd: exec.Command("sh", "-c", "lsb_release -cs")}
270         cmd.ExecuteCommand()
271         distVersion := cmd.GetStdOutput()
272         if distVersion == "" {
273                 return fmt.Errorf("ubuntu dist version not available")
274         }
275         fmt.Println("Ubuntu distribution version is", distVersion)
276         distVersionForSuite := distVersion
277         if distVersion == UbuntuBionic {
278                 // No bionic-specific version is available on apt.kubernetes.io.
279                 // Use xenial version instead.
280                 distVersionForSuite = UbuntuXenial
281         }
282         suite := fmt.Sprintf("kubernetes-%s", distVersionForSuite)
283         fmt.Println("Deb suite to use:", suite)
284
285         //Do an apt-get update
286         stdout, err := runCommandWithShell("apt-get update")
287         if err != nil {
288                 return err
289         }
290         fmt.Println(stdout)
291
292         //curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
293         //Get the GPG key
294         curl := fmt.Sprintf("curl -s %s | apt-key add -", KubernetesGPGURL)
295         cmd = &Command{Cmd: exec.Command("sh", "-c", curl)}
296         cmd.ExecuteCommand()
297         curlOutput := cmd.GetStdOutput()
298         curlErr := cmd.GetStdErr()
299         if curlOutput == "" || curlErr != "" {
300                 return fmt.Errorf("not able add the apt key due to error : %s", curlErr)
301         }
302         fmt.Println(curlOutput)
303
304         //Add K8S repo to local apt-get source.list
305         aptRepo := fmt.Sprintf("deb %s %s main", KubernetesDownloadURL, suite)
306         updtRepo := fmt.Sprintf("echo \"%s\" > /etc/apt/sources.list.d/kubernetes.list", aptRepo)
307         cmd = &Command{Cmd: exec.Command("sh", "-c", updtRepo)}
308         cmd.ExecuteCommand()
309         updtRepoErr := cmd.GetStdErr()
310         if updtRepoErr != "" {
311                 return fmt.Errorf("not able add update repo due to error : %s", updtRepoErr)
312         }
313
314         //Do an apt-get update
315         stdout, err = runCommandWithShell("apt-get update")
316         if err != nil {
317                 return err
318         }
319         fmt.Println(stdout)
320         return nil
321 }
322
323 //InstallK8S will install kubeadm, kudectl and kubelet for the cloud node
324 func (u *UbuntuOS) InstallK8S() error {
325         k8sComponent := "kubeadm"
326         fmt.Println("Installing", k8sComponent, u.KubernetesVersion, "version")
327
328         //Get the exact version string from OS repo, so that it can search and install.
329         chkKubeadmVer := fmt.Sprintf("apt-cache madison '%s' | grep %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", k8sComponent, u.KubernetesVersion)
330         cmd := &Command{Cmd: exec.Command("sh", "-c", chkKubeadmVer)}
331         cmd.ExecuteCommand()
332         stdout := cmd.GetStdOutput()
333         errout := cmd.GetStdErr()
334         if errout != "" {
335                 return fmt.Errorf("%s", errout)
336         }
337
338         fmt.Println("Expected K8S('", k8sComponent, "') version to install is", stdout)
339
340         //Install respective K8S components based on where it is being installed
341         k8sInst := fmt.Sprintf("apt-get install -y --allow-change-held-packages --allow-downgrades kubeadm=%s kubelet=%s kubectl=%s", stdout, stdout, stdout)
342         stdout, err := runCommandWithShell(k8sInst)
343         if err != nil {
344                 return err
345         }
346         fmt.Println(stdout)
347
348         fmt.Println(k8sComponent, "version", u.KubernetesVersion, "is installed in this Host")
349
350         return nil
351 }
352
353 //StartK8Scluster will do "kubeadm init" and cluster will be started
354 func (u *UbuntuOS) StartK8Scluster() error {
355         var install bool
356         cmd := &Command{Cmd: exec.Command("sh", "-c", "kubeadm version")}
357         cmd.ExecuteCommand()
358         str := cmd.GetStdOutput()
359         if str != "" {
360                 install = true
361         } else {
362                 install = false
363         }
364         if install == true {
365                 k8sInit := fmt.Sprintf("swapoff -a && kubeadm init --image-repository  \"%s\" --pod-network-cidr=%s", u.K8SImageRepository, u.K8SPodNetworkCidr)
366                 stdout, err := runCommandWithShell(k8sInit)
367                 if err != nil {
368                         return err
369                 }
370                 fmt.Println(stdout)
371
372                 stdout, err = runCommandWithShell("mkdir -p $HOME/.kube && cp -r /etc/kubernetes/admin.conf $HOME/.kube/config &&  sudo chown $(id -u):$(id -g) $HOME/.kube/config")
373                 if err != nil {
374                         return err
375                 }
376                 fmt.Println(stdout)
377         } else {
378                 return fmt.Errorf("kubeadm not installed in this host")
379         }
380         fmt.Println("Kubeadm init successfully executed")
381         return nil
382 }
383 // // runCommandWithShell executes the given command with "sh -c".
384 // // It returns an error if the command outputs anything on the stderr.
385 // func runCommandWithShell(command string) (string, error) {
386 //      cmd := &Command{Cmd: exec.Command("sh", "-c", command)}
387 //      err := cmd.ExecuteCmdShowOutput()
388 //      if err != nil {
389 //              return "", err
390 //      }
391 //      errout := cmd.GetStdErr()
392 //      if errout != "" {
393 //              return "", fmt.Errorf("%s", errout)
394 //      }
395 //      return cmd.GetStdOutput(), nil
396 // }