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

mongodb / mongodb-atlas-cli / 25848964073

14 May 2026 07:59AM UTC coverage: 22.479% (-41.3%) from 63.771%
25848964073

push

github

web-flow
build(deps): bump test-summary/action from 2.4 to 2.6 (#4576)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

8987 of 39979 relevant lines covered (22.48%)

0.25 hits per line

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

33.33
/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
        "context"
19
        "errors"
20
        "fmt"
21
        "os"
22
        "os/exec"
23
        "path"
24
        "strings"
25

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

34
var (
35
        errCreateDefaultPluginDir = errors.New("failed to create default plugin directory")
36
        minimumPluginVersions     = map[Github]string{
37
                {Owner: "mongodb", Name: "atlas-cli-plugin-kubernetes"}: "v1.1.7",
38
                {Owner: "mongodb", Name: "atlas-cli-plugin-gsa"}:        "v0.0.2", // TODO: ensure this version is correct version after work in CLOUDP-333246
39
        }
40
)
41

42
const (
43
        PluginSourceType = "plugin"
44
        sourceType       = "sourceType"
45
        sourcePluginName = "sourcePluginName"
46
)
47

48
func IsPluginCmd(cmd *cobra.Command) bool {
1✔
49
        if cmdSourceType, ok := cmd.Annotations[sourceType]; ok && cmdSourceType == PluginSourceType {
2✔
50
                return true
1✔
51
        }
1✔
52
        return false
1✔
53
}
54

55
func GetPluginWithName(name string, existingCommandsSet set.Set[string], onlySearchValidPlugins bool) (*Plugin, error) {
×
56
        var plugins []*Plugin
×
57
        if onlySearchValidPlugins {
×
58
                plugins = GetAllPluginsValidated(existingCommandsSet).GetValidPlugins()
×
59
        } else {
×
60
                plugins = getAllPlugins()
×
61
        }
×
62

63
        for _, plugin := range plugins {
×
64
                if plugin.Name == name {
×
65
                        return plugin, nil
×
66
                }
×
67
        }
68

69
        return nil, fmt.Errorf("could not find plugin %s", name)
×
70
}
71

72
func getAllPlugins() []*Plugin {
×
73
        // Load manifests from plugin directories
×
74
        manifests := loadManifestsFromPluginDirectories()
×
75

×
76
        // Convert manifests to plugins
×
77
        plugins := convertManifestsToPlugins(manifests)
×
78

×
79
        return plugins
×
80
}
×
81

82
type ValidatedPlugins struct {
83
        ValidPlugins                     []*Plugin
84
        PluginsWithDuplicateManifestName []*Plugin
85
        PluginsWithDuplicateCommands     []*Plugin
86
}
87

88
func (v *ValidatedPlugins) GetValidPlugins() []*Plugin {
×
89
        return v.ValidPlugins
×
90
}
×
91

92
func (v *ValidatedPlugins) GetValidAndInvalidPlugins() []*Plugin {
×
93
        return append(append(v.ValidPlugins, v.PluginsWithDuplicateManifestName...), v.PluginsWithDuplicateCommands...)
×
94
}
×
95

96
func GetAllPluginsValidated(existingCommandsSet set.Set[string]) *ValidatedPlugins {
×
97
        // Load manifests from plugin directories
×
98
        manifests := loadManifestsFromPluginDirectories()
×
99

×
100
        // Remove manifests with duplicate names
×
101
        manifests, duplicateManifestNames := removeManifestsWithDuplicateNames(manifests)
×
102
        duplicateManifests := make([]*Manifest, 0, len(duplicateManifestNames))
×
103
        for _, duplicate := range duplicateManifestNames {
×
104
                duplicateManifests = append(duplicateManifests, duplicate.Manifest)
×
105
                logPluginWarning(`could not load plugin "%s" because there are multiple plugins with that name`, duplicate.DuplicateName)
×
106
        }
×
107

108
        // Remove manifests that contain already existing commands
109
        manifests, duplicateManifest := getUniqueManifests(manifests, existingCommandsSet)
×
110
        duplicateCommands := make([]*Manifest, 0, len(duplicateManifest))
×
111
        for _, manifest := range duplicateManifest {
×
112
                duplicateCommands = append(duplicateCommands, manifest)
×
113
                logPluginWarning(`could not load plugin "%s" because it contains a command that already exists in the AtlasCLI or another plugin`, manifest.Name)
×
114
        }
×
115

116
        // Convert manifests to validated plugins
117
        return &ValidatedPlugins{
×
118
                ValidPlugins:                     convertManifestsToPlugins(manifests),
×
119
                PluginsWithDuplicateManifestName: convertManifestsToPlugins(duplicateManifests),
×
120
                PluginsWithDuplicateCommands:     convertManifestsToPlugins(duplicateCommands),
×
121
        }
×
122
}
123

124
func GetDefaultPluginDirectory() (string, error) {
×
125
        configHome, err := config.CLIConfigHome()
×
126

×
127
        if err != nil {
×
128
                return "", fmt.Errorf("failed to retrieve CLI config home: %w", err)
×
129
        }
×
130

131
        pluginDirectoryPath := path.Join(configHome, "plugins")
×
132

×
133
        err = os.MkdirAll(pluginDirectoryPath, os.ModePerm)
×
134
        if err != nil {
×
135
                return "", errCreateDefaultPluginDir
×
136
        }
×
137

138
        return pluginDirectoryPath, nil
×
139
}
140

141
type Command struct {
142
        Name        string
143
        Description string
144
        Aliases     []string
145
}
146

147
type Github struct {
148
        Owner string
149
        Name  string
150
}
151

152
func (g *Github) Equals(owner string, name string) bool {
×
153
        if g.Owner == owner && g.Name == name {
×
154
                return true
×
155
        }
×
156

157
        return false
×
158
}
159

160
type Plugin struct {
161
        Name                string
162
        Description         string
163
        PluginDirectoryPath string
164
        BinaryName          string
165
        Version             *semver.Version
166
        Commands            []*Command
167
        Github              *Github
168
}
169

170
func (p *Plugin) Run(cmd *cobra.Command, args []string) error {
×
171
        if err := ValidateVersion(*p.Github, p.Version); err != nil {
×
172
                return err
×
173
        }
×
174

175
        p.setTelemetry()
×
176

×
177
        binaryPath := path.Join(p.PluginDirectoryPath, p.BinaryName)
×
178
        execCmd := exec.CommandContext(context.Background(), binaryPath, append([]string{cmd.Use}, args...)...)
×
179
        execCmd.Stdin = cmd.InOrStdin()
×
180
        execCmd.Stdout = cmd.OutOrStdout()
×
181
        execCmd.Stderr = cmd.OutOrStderr()
×
182
        execCmd.Env = os.Environ()
×
183
        if err := execCmd.Run(); err != nil {
×
184
                var exitErr *exec.ExitError
×
185
                if errors.As(err, &exitErr) {
×
186
                        cmd.SilenceErrors = true
×
187
                        _, _ = log.Debugf("Silenced error: %v", exitErr)
×
188
                }
×
189
                return err
×
190
        }
191
        return nil
×
192
}
193

194
func (p *Plugin) Uninstall() error {
×
195
        return os.RemoveAll(p.PluginDirectoryPath)
×
196
}
×
197

198
func (p *Plugin) HasGithub() bool {
×
199
        return p.Github != nil && p.Github.Name != "" && p.Github.Owner != ""
×
200
}
×
201

202
func (p *Plugin) GetCobraCommands() []*cobra.Command {
1✔
203
        commands := make([]*cobra.Command, 0, len(p.Commands))
1✔
204

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

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

228
                command.DisableFlagParsing = true
1✔
229

1✔
230
                commands = append(commands, command)
1✔
231
        }
232

233
        return commands
1✔
234
}
235

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

247
        return plugins
×
248
}
249

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

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

1✔
265
        if manifest.Github != nil {
2✔
266
                plugin.Github = &Github{
1✔
267
                        Owner: strings.TrimSuffix(manifest.Github.Owner, "/"),
1✔
268
                        Name:  strings.TrimSuffix(manifest.Github.Name, "/"),
1✔
269
                }
1✔
270
        }
1✔
271

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

276
        return &plugin, nil
1✔
277
}
278

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

283
func (p *Plugin) setTelemetry() {
×
284
        info := telemetry.PluginExecutionInfo{
×
285
                Version: p.Version,
×
286
        }
×
287

×
288
        if p.Github != nil {
×
289
                info.GithubOwner = &p.Github.Owner
×
290
                info.GithubRepository = &p.Github.Name
×
291
        }
×
292

293
        telemetry.AppendOption(telemetry.WithPluginExecutionInfo(info))
×
294
}
295

296
// ValidateVersion validates the version of a plugin against the minimum required version.
297
// If a plugin is not listed in the minimumPluginVersions map, it is considered valid.
298
func ValidateVersion(gh Github, version *semver.Version) error {
1✔
299
        minVersionStr, exists := minimumPluginVersions[gh]
1✔
300
        if !exists {
2✔
301
                return nil // No version requirement for this plugin
1✔
302
        }
1✔
303

304
        minVersion, err := semver.NewVersion(minVersionStr)
1✔
305
        if err != nil {
1✔
306
                return err
×
307
        }
×
308

309
        if version.LessThan(minVersion) {
2✔
310
                return fmt.Errorf("plugin %s/%s version v%s is below minimum required version %s for this version of AtlasCLI.\nPlease update the plugin using 'atlas plugin update %s'",
1✔
311
                        gh.Owner, gh.Name, version.String(), minVersionStr, gh.Name)
1✔
312
        }
1✔
313

314
        return nil
1✔
315
}
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