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

mongodb / mongodb-atlas-cli / 16677365954

01 Aug 2025 01:40PM UTC coverage: 57.846% (-0.1%) from 57.953%
16677365954

Pull #4075

github

cveticm
Addes unit tests
Pull Request #4075: CLOUDP-330236: Adds NewServiceAccountTransport

0 of 12 new or added lines in 1 file covered. (0.0%)

518 existing lines in 22 files now uncovered.

23774 of 41099 relevant lines covered (57.85%)

2.71 hits per line

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

79.07
/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/mongodb-atlas-cli/atlascli/internal/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/spf13/cobra"
30
)
31

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

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

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

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

57
        for _, plugin := range plugins {
2✔
58
                if plugin.Name == name {
2✔
59
                        return plugin, nil
1✔
60
                }
1✔
61
        }
62

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

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

1✔
70
        // Convert manifests to plugins
1✔
71
        plugins := convertManifestsToPlugins(manifests)
1✔
72

1✔
73
        return plugins
1✔
74
}
1✔
75

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

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

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

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

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

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

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

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

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

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

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

132
        return pluginDirectoryPath, nil
1✔
133
}
134

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

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

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

151
        return false
×
152
}
153

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

164
func (p *Plugin) Run(cmd *cobra.Command, args []string) error {
1✔
165
        p.setTelemetry()
1✔
166

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

187
func (p *Plugin) Uninstall() error {
1✔
188
        return os.RemoveAll(p.PluginDirectoryPath)
1✔
189
}
1✔
190

191
func (p *Plugin) HasGithub() bool {
1✔
192
        return p.Github != nil && p.Github.Name != "" && p.Github.Owner != ""
1✔
193
}
1✔
194

195
func (p *Plugin) GetCobraCommands() []*cobra.Command {
1✔
196
        commands := make([]*cobra.Command, 0, len(p.Commands))
1✔
197

1✔
198
        for _, pluginCmd := range p.Commands {
2✔
199
                command := &cobra.Command{
1✔
200
                        Use:   pluginCmd.Name,
1✔
201
                        Short: pluginCmd.Description,
1✔
202
                        Annotations: map[string]string{
1✔
203
                                sourceType:       PluginSourceType,
1✔
204
                                sourcePluginName: p.Name,
1✔
205
                        },
1✔
206
                        RunE:    p.Run,
1✔
207
                        Aliases: pluginCmd.Aliases,
1✔
208
                }
1✔
209

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

221
                command.DisableFlagParsing = true
1✔
222

1✔
223
                commands = append(commands, command)
1✔
224
        }
225

226
        return commands
1✔
227
}
228

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

240
        return plugins
1✔
241
}
242

243
func createPluginFromManifest(manifest *Manifest) (*Plugin, error) {
1✔
244
        version, err := semver.NewVersion(manifest.Version)
1✔
245
        if err != nil {
1✔
246
                return nil, fmt.Errorf("invalid version in manifest file %s", manifest.Name)
×
247
        }
×
248

249
        plugin := Plugin{
1✔
250
                Name:                manifest.Name,
1✔
251
                Description:         manifest.Description,
1✔
252
                PluginDirectoryPath: manifest.PluginDirectoryPath,
1✔
253
                BinaryName:          manifest.Binary,
1✔
254
                Version:             version,
1✔
255
                Commands:            make([]*Command, 0, len(manifest.Commands)),
1✔
256
        }
1✔
257

1✔
258
        if manifest.Github != nil {
2✔
259
                plugin.Github = &Github{
1✔
260
                        Owner: manifest.Github.Owner,
1✔
261
                        Name:  manifest.Github.Name,
1✔
262
                }
1✔
263
        }
1✔
264

265
        for cmdName, value := range manifest.Commands {
2✔
266
                plugin.Commands = append(plugin.Commands, &Command{Name: cmdName, Description: value.Description, Aliases: value.Aliases})
1✔
267
        }
1✔
268

269
        return &plugin, nil
1✔
270
}
271

272
func logPluginWarning(message string, args ...any) {
×
273
        _, _ = log.Warningf(fmt.Sprintf("-- plugin warning: %s\n", message), args...)
×
274
}
×
275

276
func (p *Plugin) setTelemetry() {
1✔
277
        info := telemetry.PluginExecutionInfo{
1✔
278
                Version: p.Version,
1✔
279
        }
1✔
280

1✔
281
        if p.Github != nil {
2✔
282
                info.GithubOwner = &p.Github.Owner
1✔
283
                info.GithubRepository = &p.Github.Name
1✔
284
        }
1✔
285

286
        telemetry.AppendOption(telemetry.WithPluginExecutionInfo(info))
1✔
287
}
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