forked from cloudfoundry/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
install.go
207 lines (167 loc) · 5.39 KB
/
install.go
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
package pluginaction
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"code.cloudfoundry.org/cli/api/plugin"
"code.cloudfoundry.org/cli/util/configv3"
"code.cloudfoundry.org/cli/util/generic"
"code.cloudfoundry.org/gofileutils/fileutils"
)
//go:generate counterfeiter . PluginMetadata
type PluginMetadata interface {
GetMetadata(pluginPath string) (configv3.Plugin, error)
}
//go:generate counterfeiter . CommandList
type CommandList interface {
HasCommand(string) bool
HasAlias(string) bool
}
// PluginInvalidError is returned with a plugin is invalid because it is
// missing a name or has 0 commands.
type PluginInvalidError struct {
Err error
}
func (PluginInvalidError) Error() string {
return "File is not a valid cf CLI plugin binary."
}
// PluginCommandConflictError is returned when a plugin command name conflicts
// with a core or existing plugin command name.
type PluginCommandsConflictError struct {
PluginName string
PluginVersion string
CommandAliases []string
CommandNames []string
}
func (PluginCommandsConflictError) Error() string {
return ""
}
// CreateExecutableCopy makes a temporary copy of a plugin binary and makes it
// executable.
//
// config.PluginHome() + /temp is used as the temp dir instead of the system
// temp for security reasons.
func (actor Actor) CreateExecutableCopy(path string, tempPluginDir string) (string, error) {
tempFile, err := makeTempFile(tempPluginDir)
if err != nil {
return "", err
}
// add '.exe' to the temp file if on Windows
executablePath := generic.ExecutableFilename(tempFile.Name())
err = os.Rename(tempFile.Name(), executablePath)
if err != nil {
return "", err
}
err = fileutils.CopyPathToPath(path, executablePath)
if err != nil {
return "", err
}
err = os.Chmod(executablePath, 0700)
if err != nil {
return "", err
}
return executablePath, nil
}
// DownloadBinaryFromURL fetches a plugin binary from the specified URL, if
// it exists.
func (actor Actor) DownloadExecutableBinaryFromURL(pluginURL string, tempPluginDir string, proxyReader plugin.ProxyReader) (string, error) {
tempFile, err := makeTempFile(tempPluginDir)
if err != nil {
return "", err
}
err = actor.client.DownloadPlugin(pluginURL, tempFile.Name(), proxyReader)
if err != nil {
return "", err
}
return tempFile.Name(), nil
}
// FileExists returns true if the file exists. It returns false if the file
// doesn't exist or there is an error checking.
func (actor Actor) FileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func (actor Actor) GetAndValidatePlugin(pluginMetadata PluginMetadata, commandList CommandList, path string) (configv3.Plugin, error) {
plugin, err := pluginMetadata.GetMetadata(path)
if err != nil || plugin.Name == "" || len(plugin.Commands) == 0 {
return configv3.Plugin{}, PluginInvalidError{Err: err}
}
installedPlugins := actor.config.Plugins()
conflictingNames := []string{}
conflictingAliases := []string{}
for _, command := range plugin.Commands {
if commandList.HasCommand(command.Name) || commandList.HasAlias(command.Name) {
conflictingNames = append(conflictingNames, command.Name)
}
if commandList.HasAlias(command.Alias) || commandList.HasCommand(command.Alias) {
conflictingAliases = append(conflictingAliases, command.Alias)
}
for _, installedPlugin := range installedPlugins {
// we do not error if a plugins commands conflict with previous
// versions of the same plugin
if plugin.Name == installedPlugin.Name {
continue
}
for _, installedCommand := range installedPlugin.Commands {
if command.Name == installedCommand.Name || command.Name == installedCommand.Alias {
conflictingNames = append(conflictingNames, command.Name)
}
if command.Alias != "" &&
(command.Alias == installedCommand.Alias || command.Alias == installedCommand.Name) {
conflictingAliases = append(conflictingAliases, command.Alias)
}
}
}
}
if len(conflictingNames) > 0 || len(conflictingAliases) > 0 {
sort.Slice(conflictingNames, func(i, j int) bool {
return strings.ToLower(conflictingNames[i]) < strings.ToLower(conflictingNames[j])
})
sort.Slice(conflictingAliases, func(i, j int) bool {
return strings.ToLower(conflictingAliases[i]) < strings.ToLower(conflictingAliases[j])
})
return configv3.Plugin{}, PluginCommandsConflictError{
PluginName: plugin.Name,
PluginVersion: plugin.Version.String(),
CommandNames: conflictingNames,
CommandAliases: conflictingAliases,
}
}
return plugin, nil
}
func (actor Actor) InstallPluginFromPath(path string, plugin configv3.Plugin) error {
installPath := generic.ExecutableFilename(filepath.Join(actor.config.PluginHome(), plugin.Name))
err := fileutils.CopyPathToPath(path, installPath)
if err != nil {
return err
}
// rwxr-xr-x so that multiple users can share the same $CF_PLUGIN_HOME
err = os.Chmod(installPath, 0755)
if err != nil {
return err
}
plugin.Location = installPath
actor.config.AddPlugin(plugin)
err = actor.config.WritePluginConfig()
if err != nil {
return err
}
return nil
}
func (actor Actor) IsPluginInstalled(pluginName string) bool {
_, isInstalled := actor.config.GetPlugin(pluginName)
return isInstalled
}
func makeTempFile(tempDir string) (*os.File, error) {
tempFile, err := ioutil.TempFile(tempDir, "")
if err != nil {
return nil, err
}
err = tempFile.Close()
if err != nil {
return nil, err
}
return tempFile, nil
}