Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(mongosh): Support mongosh executable for connecting to a MongoDB database #283

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0dfdf01
Introduce a new arguments provisioner and a new chained provision tha…
arunsathiya Jun 8, 2023
4bc9efd
Reintroduce AddArgs helper function and fix FileProvisioner breakage
arunsathiya Jun 9, 2023
c4b198d
Initial setup with DatabaseCredentials credential and mongosh executable
arunsathiya Jun 8, 2023
e602f5f
Configure arguments to provision and index to provision at
arunsathiya Jun 8, 2023
bb7d075
Branch out MongoDB Atlas and MongoDB Shell as separate shell plugins,…
arunsathiya Jun 12, 2023
d0b7f37
Switch MongoDB Shell provision from map-based struct to string, becau…
arunsathiya Jun 13, 2023
311263a
Test to verify the provisioned arguments and expected command line ar…
arunsathiya Jun 13, 2023
dca6e83
Remove previously introduced but currently unused generic provisioner…
arunsathiya Jun 13, 2023
a31fa56
Require authentication for just mongosh command
arunsathiya Jun 13, 2023
a4b3436
Mark password field as mandatory for now. It's not technically mandat…
arunsathiya Jun 13, 2023
c4244cb
Remove Importer at the CredentialUsage-level because in the current i…
arunsathiya Jun 13, 2023
33c12bf
Rename mongodbshell plugin to mongodb
arunsathiya Jun 13, 2023
12d0967
Refactor argument provisioning logic to avoid string splitting
arunsathiya Jun 14, 2023
c540690
Skip 1Password authentication when an user enters custom host, port, …
arunsathiya Jun 14, 2023
eecbebe
Look for ConnectionString field in a 1Password item, and if it exists…
arunsathiya Jun 14, 2023
ae5c946
Fix provisoner test
arunsathiya Jun 16, 2023
5ab2c6a
Safeguard in the arguments injection code to prevent out of bounds er…
arunsathiya Jun 16, 2023
d92c6b9
Set default provisioner to NoOp for now for validation checks to pass…
arunsathiya Jun 16, 2023
0999979
Switch default importer to noop
Jun 16, 2023
513bd6e
Don't skip auth for certain flags, rather use them in conjunction wit…
arunsathiya Aug 17, 2023
faaedf3
Support both standard connection string format and SRV connection format
arunsathiya Aug 17, 2023
08e27c6
Fix arguments for host and port. These arguments are used when connec…
arunsathiya Aug 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions plugins/mongodb/database_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package mongodb

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/importer"
"github.com/1Password/shell-plugins/sdk/provision"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

func DatabaseCredentials() schema.CredentialType {
return schema.CredentialType{
arunsathiya marked this conversation as resolved.
Show resolved Hide resolved
Name: credname.DatabaseCredentials,
DocsURL: sdk.URL("https://www.mongodb.com/docs/mongodb-shell/connect/"),
Fields: []schema.CredentialField{
{
Name: fieldname.ConnectionString,
MarkdownDescription: "Connection String for the MongoDB database.",
Secret: false,
Optional: true,
Composition: &schema.ValueComposition{
Charset: schema.Charset{
Lowercase: true,
Digits: true,
Symbols: true,
},
},
},
{
Name: fieldname.Host,
MarkdownDescription: "Host address for the MongoDB database.",
Secret: false,
Optional: true,
Composition: &schema.ValueComposition{
Charset: schema.Charset{
Lowercase: true,
Digits: true,
Specific: []rune{'.', '-'},
},
},
},
{
Name: fieldname.Port,
MarkdownDescription: "Port for the MongoDB database.",
Secret: false,
Optional: true,
Composition: &schema.ValueComposition{
Charset: schema.Charset{
Digits: true,
},
},
},
{
Name: fieldname.Username,
MarkdownDescription: "Username for authenticating to the MongoDB database.",
Secret: false,
Optional: true,
Composition: &schema.ValueComposition{
Charset: schema.Charset{
Lowercase: true,
Digits: true,
Specific: []rune{'-', '_'},
},
},
},
{
Name: fieldname.Password,
MarkdownDescription: "Password for authenticating to the MongoDB database.",
Secret: true,
Optional: false,
Composition: &schema.ValueComposition{
Charset: schema.Charset{
Lowercase: true,
Uppercase: true,
Digits: true,
},
},
},
},
DefaultProvisioner: provision.NoOp(),
Importer: importer.NoOp(),
}
}
26 changes: 26 additions & 0 deletions plugins/mongodb/database_credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mongodb

import (
"testing"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/plugintest"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

func TestDatabaseCredentialsProvisioner(t *testing.T) {
plugintest.TestProvisioner(t, mongodbShellProvisioner(), map[string]plugintest.ProvisionCase{
"default": {
ItemFields: map[sdk.FieldName]string{
fieldname.Host: "localhost",
fieldname.Port: "27017",
fieldname.Username: "default",
fieldname.Password: "password",
},
CommandLine: []string{"mongosh"},
ExpectedOutput: sdk.ProvisionOutput{
CommandLine: []string{"mongosh", "--password", "password", "--username", "default", "--port", "27017", "--host", "localhost"}, // Each argument is provisioned at index 1, pushing the existing arguments forward to the next index
},
},
})
}
25 changes: 25 additions & 0 deletions plugins/mongodb/mongosh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mongodb

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/needsauth"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
)

func MongoshCLI() schema.Executable {
return schema.Executable{
Name: "MongoDB Shell",
Runs: []string{"mongosh"},
DocsURL: sdk.URL("https://www.mongodb.com/docs/mongodb-shell/"),
NeedsAuth: needsauth.IfAll(
needsauth.NotForHelpOrVersion(),
),
Uses: []schema.CredentialUsage{
{
Name: credname.DatabaseCredentials,
Provisioner: mongodbShellProvisioner(),
},
},
}
}
22 changes: 22 additions & 0 deletions plugins/mongodb/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mongodb

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/schema"
)

func New() schema.Plugin {
return schema.Plugin{
Name: "mongodb",
Platform: schema.PlatformInfo{
Name: "MongoDB Shell",
Homepage: sdk.URL("https://www.mongodb.com/products/shell"),
},
Credentials: []schema.CredentialType{
DatabaseCredentials(),
},
Executables: []schema.Executable{
MongoshCLI(),
},
}
}
105 changes: 105 additions & 0 deletions plugins/mongodb/provisioner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package mongodb

import (
"context"
"strings"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

type mongodbShellArgsProvisioner struct {
}

func mongodbShellProvisioner() sdk.Provisioner {
return mongodbShellArgsProvisioner{}
}

func (p mongodbShellArgsProvisioner) Provision(ctx context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) {
listOfPossibleUserInputArguments := []string{
"--host",
"--port",
"-u", "--username",
"-p", "--password",
}

var (
commandLineContainsHostArgument bool = false
commandLineContainsPortArgument bool = false
commandLineContainsUsernameArgument bool = false
commandLineContainsPasswordArgument bool = false
)

connectionStringProvisioned := false

for _, arg := range out.CommandLine {
if strings.HasPrefix(arg, "mongodb://") || strings.HasPrefix(arg, "mongodb+srv://") {
connectionStringProvisioned = true
break
}
}

if value, ok := in.ItemFields[fieldname.ConnectionString]; ok && !connectionStringProvisioned {
out.AddArgs(value)
connectionStringProvisioned = true
}

for i, arg := range out.CommandLine {
for _, userArg := range listOfPossibleUserInputArguments {
if arg == userArg {
// Get the executable "mongosh", the matched argument we are looking for and its value
commandLine := []string{out.CommandLine[0], out.CommandLine[i], out.CommandLine[i+1]}
// Remove the matched argument and its value as they will be added to the beginning of the command line
out.CommandLine = append(out.CommandLine[:i], out.CommandLine[i+2:]...)
// Add the executable "mongosh", the matched argument and its value to the beginning of the command line
commandLine = append(commandLine, out.CommandLine[1:]...)
out.CommandLine = commandLine
// Controller to check if the user has already provided the argument
switch userArg {
case "--host":
commandLineContainsHostArgument = true
case "--port":
commandLineContainsPortArgument = true
case "-u", "--username":
commandLineContainsUsernameArgument = true
case "-p", "--password":
commandLineContainsPasswordArgument = true
default:
break
}
}
}
}

if value, ok := in.ItemFields[fieldname.Host]; ok && !commandLineContainsHostArgument && !connectionStringProvisioned {
commandLine := []string{out.CommandLine[0], "--host", value}
commandLine = append(commandLine, out.CommandLine[1:]...)
out.CommandLine = commandLine
}

if value, ok := in.ItemFields[fieldname.Port]; ok && !commandLineContainsPortArgument && !connectionStringProvisioned {
commandLine := []string{out.CommandLine[0], "--port", value}
commandLine = append(commandLine, out.CommandLine[1:]...)
out.CommandLine = commandLine
}

if value, ok := in.ItemFields[fieldname.Username]; ok && !commandLineContainsUsernameArgument {
commandLine := []string{out.CommandLine[0], "--username", value}
commandLine = append(commandLine, out.CommandLine[1:]...)
out.CommandLine = commandLine
}

if value, ok := in.ItemFields[fieldname.Password]; ok && !commandLineContainsPasswordArgument {
commandLine := []string{out.CommandLine[0], "--password", value}
commandLine = append(commandLine, out.CommandLine[1:]...)
out.CommandLine = commandLine
}
}

func (p mongodbShellArgsProvisioner) Deprovision(ctx context.Context, in sdk.DeprovisionInput, out *sdk.DeprovisionOutput) {
// Nothing to do here: credentials get wiped automatically when the process exits.
}

func (p mongodbShellArgsProvisioner) Description() string {
return "Provision MongoDB Shell secrets as command-line arguments."
}
2 changes: 1 addition & 1 deletion plugins/atlas/apikey.go → plugins/mongodbatlas/apikey.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package atlas
package mongodbatlas

import (
"github.com/1Password/shell-plugins/sdk"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package atlas
package mongodbatlas

import (
"testing"
Expand Down
2 changes: 1 addition & 1 deletion plugins/atlas/atlas.go → plugins/mongodbatlas/atlas.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package atlas
package mongodbatlas

import (
"github.com/1Password/shell-plugins/sdk"
Expand Down
4 changes: 2 additions & 2 deletions plugins/atlas/plugin.go → plugins/mongodbatlas/plugin.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package atlas
package mongodbatlas

import (
"github.com/1Password/shell-plugins/sdk"
Expand All @@ -7,7 +7,7 @@ import (

func New() schema.Plugin {
return schema.Plugin{
Name: "atlas",
Name: "mongodbatlas",
Platform: schema.PlatformInfo{
Name: "MongoDB Atlas",
Homepage: sdk.URL("https://www.mongodb.com/"),
Expand Down
14 changes: 14 additions & 0 deletions sdk/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ func (out *ProvisionOutput) AddArgs(args ...string) {
out.CommandLine = append(out.CommandLine, args...)
}

// AddArgsAtIndex can be used to add additional arguments to the command line of the provision output, at a specific index.
func (out *ProvisionOutput) AddArgsAtIndex(index uint, args ...string) {
arunsathiya marked this conversation as resolved.
Show resolved Hide resolved
// Provision arguments at the end of the command line input to prevent out of bound errors. But, this isn't entirely a concern is the case of mongosh where we always provision at index 1 and "mongosh" is the minimum-required command
if index >= uint(len(out.CommandLine)) {
out.CommandLine = append(out.CommandLine, args...)
return
}
newCommandLine := []string{}
newCommandLine = append(newCommandLine, out.CommandLine[:index]...)
newCommandLine = append(newCommandLine, args...)
newCommandLine = append(newCommandLine, out.CommandLine[index:]...)
out.CommandLine = newCommandLine
}

// AddSecretFile can be used to add a file containing secrets to the provision output.
func (out *ProvisionOutput) AddSecretFile(path string, contents []byte) {
out.AddFile(path, OutputFile{
Expand Down
Loading