This repository was archived by the owner on Aug 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 604
Expand file tree
/
Copy pathuninstall.go
More file actions
287 lines (239 loc) · 8.77 KB
/
uninstall.go
File metadata and controls
287 lines (239 loc) · 8.77 KB
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"koding/klientctl/config"
configcli "koding/klientctl/endpoint/config"
"github.com/codegangsta/cli"
"github.com/koding/logging"
)
// ServiceUninstaller is used to reduce the testable size of the Service, easing
// mocking.
type ServiceUninstaller interface {
Stop() error
// Uninstall the given service
Uninstall() error
}
// Uninstall is a configurable struct which handles the uninstall
// logic for the UninstallCommand, and making it entirely configurable
// and thus testable.
type Uninstall struct {
// The uninstaller to call.
ServiceUninstaller ServiceUninstaller
// The public facing klient name (not filename).
KlientName string
// The public facing klient name (not filename).
KlientctlName string
// The full path of the klientctl binary to be removed. Note that the directory
// is not removed, as it is often a PATH complaint directory and should likely
// be left intact.
KlientctlPath string
// The parent *non-removable* directory for the KlientDirectory. Since Klient
// is often stored in nested directories, the KlientParentDirectory is the first
// directory that that is not removed. Example:
//
// KlientBin == klient
// KlientDirectory == kite/klient
// KlientParentDirectory == /opt
// FullKlientBinPath == /opt/kite/klient/klient
//
// See also: Uninstall.RemoveKlientDirectories()
KlientParentDirectory string
// *All* of the directories of the klient binary to be removed, if empty. It's
// important to note that every directory in this path will be removed. Please
// see KlientParentDirectory for additional details.
//
// Example value:
//
// kite/klient
KlientDirectory string
// the Klient binary filename to be removed.
KlientFilename string
// The klient.sh filename to be removed.
KlientshFilename string
// remover is a remove func, which can be used to play safe for testing. Typically
// equals os.Remove.
remover func(string) error
// warnings is a slice of warnings to respond to the user with. Since most
// errors during the uninstall process aren't reason alone to stop uninstalling,
// we want to simply inform the user of them. This slice keeps track of these
// warnings.
//
// IMPORTANT: These warnings are *user facing*, and should be populated from
// the errormessages.go file.
warnings []string
// The internal logger.
log logging.Logger
}
// Uninstall actually runs the uninstall process, configured by the structs fields,
// and implemented by all the individual uninstall methods.
//
// Note that Uninstall cleans up the directories if empty, but does not print
// warning messages about this. This is to avoid spam, as one failure to remove a
// bin can end up with multiple warnings, creating a bad UX.
func (u *Uninstall) Uninstall() (string, int) {
if err := u.ServiceUninstaller.Stop(); err != nil {
u.log.Warning("Service errored on stop. err:%s", err)
u.addWarning(FailedStopKlientWarn)
}
if err := u.ServiceUninstaller.Uninstall(); err != nil {
u.log.Warning("Service errored on uninstall. err:%s", err)
u.addWarning(FailedUninstallingKlientWarn)
}
// Remove the kitekey
if err := u.RemoveKiteKey(); err != nil {
u.log.Warning("Failed to remove kite key. err:%s", err)
u.addWarning(FailedToRemoveAuthFileWarn)
}
// Remove the klient/klient.sh files
if err := u.RemoveKlientFiles(); err != nil {
u.log.Warning("Failed to remove klient or klient.sh. err:%s", err)
u.addWarning(FailedToRemoveFilesWarn)
}
// Remove the klient directories
//
// Note that we're not printing any errors in removing the directories to avoid
// spamming the user with warnings.
if err := u.RemoveKlientDirectories(); err != nil {
u.log.Warning("Failed to remove klient directories. err:%s", err)
}
// Remove the klientctl binary itself.
// (The current binary is removing itself.. So emo...)
if err := u.RemoveKlientctl(); err != nil {
u.log.Warning("Failed to remove klientctl. err:%s", err)
u.addWarning(FailedToRemoveKlientWarn)
}
if len(u.warnings) != 0 {
return fmt.Sprintf(
"Successfully uninstalled %s, with warnings:\n%s",
u.KlientName, strings.Join(u.warnings, "\n"),
), 0
}
return fmt.Sprintf("Successfully uninstalled %s\n", config.KlientName), 0
}
// UninstallCommand configures the Uninstall struct and calls it based on the
// given codegangsta/cli context.
//
// TODO: remove all artifacts, ie bolt db, ssh keys, kd etc.
func UninstallCommand(c *cli.Context, log logging.Logger, _ string) (string, int) {
warnings := []string{}
// Ensure /etc/kite/kite.key is migrated to konfig.bolt before
// old klient gets uninstalled. The endpoint/config package
// performs lazy migrations, so it's enough to call any of
// its methods and disregard the result.
_ = configcli.List()
s, err := newService(nil)
if err != nil {
log.Warning("Failed creating Service for uninstall. err:%s", err)
warnings = append(warnings, FailedUninstallingKlientWarn)
}
uninstaller := &Uninstall{
ServiceUninstaller: s,
KlientName: config.KlientName,
KlientctlName: config.Name,
KlientctlPath: filepath.Join(KlientctlDirectory, KlientctlBinName),
// TODO: Store the klient directory structure(s) somewhere
KlientParentDirectory: "/opt",
KlientDirectory: "kite/klient",
KlientFilename: "klient",
KlientshFilename: "klient.sh",
remover: os.Remove,
warnings: warnings,
log: log,
}
return uninstaller.Uninstall()
}
// RemoveKiteKey removes the kite key and kitekeydirectory
func (u *Uninstall) RemoveKiteKey() error {
konfig, err := configcli.Used()
if err != nil {
return err
}
konfig.KiteKey = ""
return configcli.Use(konfig)
}
// RemoveKlientFiles removes the klient bin, klient.sh, but not the klient
// directories. For directories, see Uninstall.RemoveKlientDirectories
func (u *Uninstall) RemoveKlientFiles() error {
if u.KlientParentDirectory == "" {
return errors.New("KlientParentDirectory cannot be empty")
}
if u.KlientDirectory == "" {
return errors.New("KlientDirectory cannot be empty")
}
if u.KlientFilename == "" {
return errors.New("KlientFilename cannot be empty")
}
if u.KlientshFilename == "" {
return errors.New("KlientshFilename cannot be empty")
}
// the directory of the files
klientDir := filepath.Join(u.KlientParentDirectory, u.KlientDirectory)
// Remove klient bin
if err := u.remover(filepath.Join(klientDir, u.KlientFilename)); err != nil {
return err
}
// Remove klient.sh
if err := u.remover(filepath.Join(klientDir, u.KlientshFilename)); err != nil {
return err
}
return nil
}
// RemoveKlientDirectories removes the klient directories recursively, up until and
// not including the Uninstall.KlientParentDirectory. As an example:
//
// KlientBin == klient
// KlientDirectory == kite/klient
// KlientParentDirectory == /opt
// FullKlientBinPath == /opt/kite/klient/klient
//
// In the above, /opt/kite/klient and /opt/kite would be removed, but not
// /opt. If /opt/kite/klient is not empty, nothing is removed.
func (u *Uninstall) RemoveKlientDirectories() error {
if u.KlientDirectory == "" {
return errors.New("KlientDirectory cannot be empty")
}
if u.KlientParentDirectory == "" {
return errors.New("KlientParentDirectory cannot be empty")
}
if filepath.IsAbs(u.KlientDirectory) {
return errors.New("Cannot use absolute path directory as KlientDirectory")
}
// Remove the klient directories repeatedly up until the parent.
//
// Note that the "." check is because Dir will return the dot (current directory)
// when there are no other dirs to return. Example:
//
// filepath.Dir("foo/bar/baz") // foo/bar
// filepath.Dir("foo/bar") // foo
// filepath.Dir("foo") // .
for p := u.KlientDirectory; p != "."; p = filepath.Dir(p) {
rmP := filepath.Join(u.KlientParentDirectory, p)
// Just to be extra safe, since we're loop removing, lets confirm that it's
// not the parent dir. In multiple forms.
if rmP == u.KlientParentDirectory || filepath.Clean(rmP) == filepath.Clean(u.KlientParentDirectory) {
return errors.New("Directory to be removed equalled the KlientParentDirectory")
}
// If there is any error, return it. Generally we don't care about removal
// errors, but if anything fails we can't do anything with future parent dirs,
// so there's nothing to be done.
if err := u.remover(rmP); err != nil {
return err
}
}
return nil
}
// RemoveKlientctl removes the klient binary
// (The current binary is removing itself.. So emo...)
func (u *Uninstall) RemoveKlientctl() error {
if u.KlientctlPath == "" {
return errors.New("KlientctlPath cannot be empty")
}
return u.remover(u.KlientctlPath)
}
func (u *Uninstall) addWarning(s string) {
u.warnings = append(u.warnings, s)
}