• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb / mongodb-atlas-cli / 17211029452

25 Aug 2025 01:57PM UTC coverage: 63.284%. First build
17211029452

Pull #4160

github

cveticm
disabling plugin test
Pull Request #4160: CLOUDP-339293: Add plugin version check

13 of 20 new or added lines in 3 files covered. (65.0%)

26557 of 41965 relevant lines covered (63.28%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.29
/internal/plugin/plugin.go
1
// Copyright 2024 MongoDB Inc
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package plugin
16

17
import (
18
        "errors"
19
        "fmt"
20
        "os"
21
        "os/exec"
22
        "path"
23

24
        "github.com/Masterminds/semver/v3"
25
        "github.com/mongodb/atlas-cli-core/config"
26
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
27
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/set"
28
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry"
29
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/validate"
30
        "github.com/spf13/cobra"
31
)
32

33
var (
34
        errCreateDefaultPluginDir = errors.New("failed to create default plugin directory")
35
)
36

37
const (
38
        PluginSourceType = "plugin"
39
        sourceType       = "sourceType"
40
        sourcePluginName = "sourcePluginName"
41
)
42

43
func IsPluginCmd(cmd *cobra.Command) bool {
1✔
44
        if cmdSourceType, ok := cmd.Annotations[sourceType]; ok && cmdSourceType == PluginSourceType {
2✔
45
                return true
1✔
46
        }
1✔
47
        return false
1✔
48
}
49

50
func GetPluginWithName(name string, existingCommandsSet set.Set[string], onlySearchValidPlugins bool) (*Plugin, error) {
×
51
        var plugins []*Plugin
×
52
        if onlySearchValidPlugins {
×
53
                plugins = GetAllPluginsValidated(existingCommandsSet).GetValidPlugins()
×
54
        } else {
×
55
                plugins = getAllPlugins()
×
56
        }
×
57

58
        for _, plugin := range plugins {
×
59
                if plugin.Name == name {
×
60
                        return plugin, nil
×
61
                }
×
62
        }
63

64
        return nil, fmt.Errorf("could not find plugin %s", name)
×
65
}
66

67
func getAllPlugins() []*Plugin {
×
68
        // Load manifests from plugin directories
×
69
        manifests := loadManifestsFromPluginDirectories()
×
70

×
71
        // Convert manifests to plugins
×
72
        plugins := convertManifestsToPlugins(manifests)
×
73

×
74
        return plugins
×
75
}
×
76

77
type ValidatedPlugins struct {
78
        ValidPlugins                     []*Plugin
79
        PluginsWithDuplicateManifestName []*Plugin
80
        PluginsWithDuplicateCommands     []*Plugin
81
}
82

83
func (v *ValidatedPlugins) GetValidPlugins() []*Plugin {
1✔
84
        return v.ValidPlugins
1✔
85
}
1✔
86

87
func (v *ValidatedPlugins) GetValidAndInvalidPlugins() []*Plugin {
1✔
88
        return append(append(v.ValidPlugins, v.PluginsWithDuplicateManifestName...), v.PluginsWithDuplicateCommands...)
1✔
89
}
1✔
90

91
func GetAllPluginsValidated(existingCommandsSet set.Set[string]) *ValidatedPlugins {
1✔
92
        // Load manifests from plugin directories
1✔
93
        manifests := loadManifestsFromPluginDirectories()
1✔
94
        duplicateManifests := make([]*Manifest, 0)
1✔
95
        duplicateCommands := make([]*Manifest, 0)
1✔
96

1✔
97
        // Remove manifests with duplicate names
1✔
98
        manifests, duplicateManifestNames := removeManifestsWithDuplicateNames(manifests)
1✔
99
        for _, duplicate := range duplicateManifestNames {
1✔
100
                duplicateManifests = append(duplicateManifests, duplicate.Manifest)
×
101
                logPluginWarning(`could not load plugin "%s" because there are multiple plugins with that name`, duplicate.DuplicateName)
×
102
        }
×
103

104
        // Remove manifests that contain already existing commands
105
        manifests, duplicateManifest := getUniqueManifests(manifests, existingCommandsSet)
1✔
106
        for _, manifest := range duplicateManifest {
1✔
107
                duplicateCommands = append(duplicateCommands, manifest)
×
108
                logPluginWarning(`could not load plugin "%s" because it contains a command that already exists in the AtlasCLI or another plugin`, manifest.Name)
×
109
        }
×
110

111
        // Convert manifests to validated plugins
112
        return &ValidatedPlugins{
1✔
113
                ValidPlugins:                     convertManifestsToPlugins(manifests),
1✔
114
                PluginsWithDuplicateManifestName: convertManifestsToPlugins(duplicateManifests),
1✔
115
                PluginsWithDuplicateCommands:     convertManifestsToPlugins(duplicateCommands),
1✔
116
        }
1✔
117
}
118

119
func GetDefaultPluginDirectory() (string, error) {
1✔
120
        configHome, err := config.CLIConfigHome()
1✔
121

1✔
122
        if err != nil {
1✔
123
                return "", fmt.Errorf("failed to retrieve CLI config home: %w", err)
×
124
        }
×
125

126
        pluginDirectoryPath := path.Join(configHome, "plugins")
1✔
127

1✔
128
        err = os.MkdirAll(pluginDirectoryPath, os.ModePerm)
1✔
129
        if err != nil {
1✔
130
                return "", errCreateDefaultPluginDir
×
131
        }
×
132

133
        return pluginDirectoryPath, nil
1✔
134
}
135

136
type Command struct {
137
        Name        string
138
        Description string
139
        Aliases     []string
140
}
141

142
type Github struct {
143
        Owner string
144
        Name  string
145
}
146

147
func (g *Github) Equals(owner string, name string) bool {
1✔
148
        if g.Owner == owner && g.Name == name {
2✔
149
                return true
1✔
150
        }
1✔
151

152
        return false
×
153
}
154

155
type Plugin struct {
156
        Name                string
157
        Description         string
158
        PluginDirectoryPath string
159
        BinaryName          string
160
        Version             *semver.Version
161
        Commands            []*Command
162
        Github              *Github
163
}
164

165
func (p *Plugin) Run(cmd *cobra.Command, args []string) error {
1✔
166
        if err := validate.PluginVersion(p.Name, p.Version); err != nil {
1✔
NEW
167
                return err
×
NEW
168
        }
×
169

170
        p.setTelemetry()
1✔
171

1✔
172
        binaryPath := path.Join(p.PluginDirectoryPath, p.BinaryName)
1✔
173
        // suppressing lint error flagging potential tainted input or cmd arguments
1✔
174
        // we are this can happen, it is by design
1✔
175
        // #nosec G204
1✔
176
        execCmd := exec.Command(binaryPath, append([]string{cmd.Use}, args...)...)
1✔
177
        execCmd.Stdin = cmd.InOrStdin()
1✔
178
        execCmd.Stdout = cmd.OutOrStdout()
1✔
179
        execCmd.Stderr = cmd.OutOrStderr()
1✔
180
        execCmd.Env = os.Environ()
1✔
181
        if err := execCmd.Run(); err != nil {
1✔
182
                var exitErr *exec.ExitError
×
183
                if errors.As(err, &exitErr) {
×
184
                        cmd.SilenceErrors = true
×
185
                        _, _ = log.Debugf("Silenced error: %v", exitErr)
×
186
                }
×
187
                return err
×
188
        }
189
        return nil
1✔
190
}
191

192
func (p *Plugin) Uninstall() error {
1✔
193
        return os.RemoveAll(p.PluginDirectoryPath)
1✔
194
}
1✔
195

196
func (p *Plugin) HasGithub() bool {
1✔
197
        return p.Github != nil && p.Github.Name != "" && p.Github.Owner != ""
1✔
198
}
1✔
199

200
func (p *Plugin) GetCobraCommands() []*cobra.Command {
2✔
201
        commands := make([]*cobra.Command, 0, len(p.Commands))
2✔
202

2✔
203
        for _, pluginCmd := range p.Commands {
4✔
204
                command := &cobra.Command{
2✔
205
                        Use:   pluginCmd.Name,
2✔
206
                        Short: pluginCmd.Description,
2✔
207
                        Annotations: map[string]string{
2✔
208
                                sourceType:       PluginSourceType,
2✔
209
                                sourcePluginName: p.Name,
2✔
210
                        },
2✔
211
                        RunE:    p.Run,
2✔
212
                        Aliases: pluginCmd.Aliases,
2✔
213
                }
2✔
214

2✔
215
                // Disable the default cobra help function.
2✔
216
                // Instead redirect help to the plugin.
2✔
217
                // Example: atlas example-plugin --help -> [example-binary] example-plugin --help
2✔
218
                command.SetHelpFunc(func(cmd *cobra.Command, args []string) {
2✔
219
                        // args contains all arguments + the name of the command
×
220
                        // we don't need the name of the subcommand
×
221
                        if err := p.Run(cmd, args[1:]); err != nil {
×
222
                                _, _ = log.Warningf("failed to generate help for plugin command '%v': %v", args[0], err)
×
223
                        }
×
224
                })
225

226
                command.DisableFlagParsing = true
2✔
227

2✔
228
                commands = append(commands, command)
2✔
229
        }
230

231
        return commands
2✔
232
}
233

234
func convertManifestsToPlugins(manifests []*Manifest) []*Plugin {
1✔
235
        plugins := make([]*Plugin, 0, len(manifests))
1✔
236
        for _, manifest := range manifests {
2✔
237
                plugin, err := createPluginFromManifest(manifest)
1✔
238
                if err != nil {
1✔
239
                        logPluginWarning(err.Error())
×
240
                        continue
×
241
                }
242
                plugins = append(plugins, plugin)
1✔
243
        }
244

245
        return plugins
1✔
246
}
247

248
func createPluginFromManifest(manifest *Manifest) (*Plugin, error) {
2✔
249
        version, err := semver.NewVersion(manifest.Version)
2✔
250
        if err != nil {
2✔
251
                return nil, fmt.Errorf("invalid version in manifest file %s", manifest.Name)
×
252
        }
×
253

254
        plugin := Plugin{
2✔
255
                Name:                manifest.Name,
2✔
256
                Description:         manifest.Description,
2✔
257
                PluginDirectoryPath: manifest.PluginDirectoryPath,
2✔
258
                BinaryName:          manifest.Binary,
2✔
259
                Version:             version,
2✔
260
                Commands:            make([]*Command, 0, len(manifest.Commands)),
2✔
261
        }
2✔
262

2✔
263
        if manifest.Github != nil {
3✔
264
                plugin.Github = &Github{
1✔
265
                        Owner: manifest.Github.Owner,
1✔
266
                        Name:  manifest.Github.Name,
1✔
267
                }
1✔
268
        }
1✔
269

270
        for cmdName, value := range manifest.Commands {
4✔
271
                plugin.Commands = append(plugin.Commands, &Command{Name: cmdName, Description: value.Description, Aliases: value.Aliases})
2✔
272
        }
2✔
273

274
        return &plugin, nil
2✔
275
}
276

277
func logPluginWarning(message string, args ...any) {
×
278
        _, _ = log.Warningf(fmt.Sprintf("-- plugin warning: %s\n", message), args...)
×
279
}
×
280

281
func (p *Plugin) setTelemetry() {
1✔
282
        info := telemetry.PluginExecutionInfo{
1✔
283
                Version: p.Version,
1✔
284
        }
1✔
285

1✔
286
        if p.Github != nil {
2✔
287
                info.GithubOwner = &p.Github.Owner
1✔
288
                info.GithubRepository = &p.Github.Name
1✔
289
        }
1✔
290

291
        telemetry.AppendOption(telemetry.WithPluginExecutionInfo(info))
1✔
292
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc