Skip to content

Commit

Permalink
Merge pull request #164 from foomo/bruno
Browse files Browse the repository at this point in the history
feat(usebruno/bruno): add provider
  • Loading branch information
franklinkim authored Dec 6, 2024
2 parents a24f934 + e51a7d1 commit bec80f1
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 0 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ require (
github.com/cloudrecipes/packagejson v1.0.0
github.com/digitalocean/godo v1.131.0
github.com/foomo/posh v0.8.2
github.com/goccy/go-json v0.9.11
github.com/golang-migrate/migrate/v4 v4.18.1
github.com/google/go-github/v47 v47.1.0
github.com/joho/godotenv v1.5.1
github.com/muesli/go-app-paths v0.2.2
github.com/pkg/errors v0.9.1
github.com/pterm/pterm v0.12.80
github.com/samber/lo v1.47.0
Expand Down Expand Up @@ -53,6 +55,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/neilotoole/slogt v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand Down Expand Up @@ -123,8 +125,12 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/muesli/go-app-paths v0.2.2 h1:NqG4EEZwNIhBq/pREgfBmgDmt3h1Smr1MjZiXbpZUnI=
github.com/muesli/go-app-paths v0.2.2/go.mod h1:SxS3Umca63pcFcLtbjVb+J0oD7cl4ixQWoBKhGEtEho=
github.com/neilotoole/slogt v1.1.0 h1:c7qE92sq+V0yvCuaxph+RQ2jOKL61c4hqS1Bv9W7FZE=
github.com/neilotoole/slogt v1.1.0/go.mod h1:RCrGXkPc/hYybNulqQrMHRtvlQ7F6NktNVLuLwk6V+w=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
Expand Down
78 changes: 78 additions & 0 deletions usebruno/bruno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# POSH Bruno provider

## Usage

### Plugin

```go
package main

import (
"github.com/foomo/posh-providers/onepassword"
"github.com/foomo/posh-providers/usebruno/bruno"
"github.com/foomo/posh/pkg/cache"
"github.com/foomo/posh/pkg/command"
)

type Plugin struct {
l log.Logger
cache cache.Cache
op *onepassword.OnePassword
}

func New(l log.Logger) (plugin.Plugin, error) {
var err error
inst := &Plugin{
l: l,
cache: cache.MemoryCache{},
commands: command.Commands{},
}

// ...

inst.op, err = onepassword.New(l, inst.cache)
if err != nil {
return nil, errors.Wrap(err, "failed to create onepassword")
}

// ...

inst.commands.Add(bruno.NewCommand(l, bruno.CommandWithOnePassword(inst.op)))

// ...

return inst, nil
}

```

### Config

```yaml
## Bruno
bruno:
path: '${PROJECT_ROOT}/.posh/bruno'
```
### OnePassword
To inject secrets from 1Password, create a `bruno.env` file:

```text
JWT_TOKEN=*********************
```

Render the file to `.env`:

```shell
> bruno env
```

And use the secret in your environment `environments/local.bru`:

```text
vars {
host: http://localhost:5005
jwtToken: {{process.env.JWT_TOKEN}}
}
```
251 changes: 251 additions & 0 deletions usebruno/bruno/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package bruno

import (
"context"
"os"
"path"

"github.com/foomo/posh-providers/onepassword"
"github.com/foomo/posh/pkg/command/tree"
"github.com/foomo/posh/pkg/log"
"github.com/foomo/posh/pkg/prompt/goprompt"
"github.com/foomo/posh/pkg/readline"
"github.com/foomo/posh/pkg/shell"
"github.com/foomo/posh/pkg/util/suggests"
gap "github.com/muesli/go-app-paths"
"github.com/pkg/errors"
"github.com/pterm/pterm"
"github.com/spf13/viper"
)

type (
Command struct {
l log.Logger
op *onepassword.OnePassword
name string
appName string
config Config
configKey string
commandTree tree.Root
}
CommandOption func(*Command)
)

// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------

func CommandWithName(v string) CommandOption {
return func(o *Command) {
o.name = v
}
}

func CommandWithAppName(v string) CommandOption {
return func(o *Command) {
o.appName = v
}
}

func CommandWithConfigKey(v string) CommandOption {
return func(o *Command) {
o.configKey = v
}
}

func CommandWithOnePassword(v *onepassword.OnePassword) CommandOption {
return func(o *Command) {
o.op = v
}
}

// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------

func NewCommand(l log.Logger, opts ...CommandOption) (*Command, error) {
inst := &Command{
l: l.Named("bruno"),
name: "bruno",
appName: "Bruno",
configKey: "bruno",
}

for _, opt := range opts {
if opt != nil {
opt(inst)
}
}

if err := viper.UnmarshalKey(inst.configKey, &inst.config); err != nil {
return nil, err
}

inst.commandTree = tree.New(&tree.Node{
Name: inst.name,
Description: "Run Bruno requests",
Nodes: tree.Nodes{
{
Name: "list",
Description: "List available requests",
Execute: inst.list,
},
{
Name: "env",
Description: "Render secrets env",
Execute: inst.env,
},
{
Name: "run",
Description: "Run the Bruno cli",
Args: tree.Args{
{
Name: "env",
Description: "Environment name",
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.config.Environments())
},
},
{
Name: "request",
Description: "Request to run",
Repeat: true,
Optional: true,
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.config.Requests())
},
},
},
Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
fs.Default().String("format", "", "Environment variables")
fs.Default().String("env", "", "Overwrite a single environment variable")
fs.Default().String("env-var", "", "Overwrite a single environment variable")
fs.Default().Bool("bail", false, "Stop execution after a failure of a request, test, or assertion")
fs.Default().Bool("insecure", false, "Allow insecure server connections")
fs.Default().Bool("tests-only", false, "Only run requests that have tests")
fs.Default().Bool("verbose", false, "Allow verbose output for debugging purpose")
return nil
},
Execute: inst.run,
},
{
Name: "open",
Description: "Open the Bruno app",
Execute: inst.open,
},
},
})

return inst, nil
}

// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------

func (c *Command) Name() string {
return c.commandTree.Node().Name
}

func (c *Command) Description() string {
return c.commandTree.Node().Description
}

func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return c.commandTree.Complete(ctx, r)
}

func (c *Command) Execute(ctx context.Context, r *readline.Readline) error {
return c.commandTree.Execute(ctx, r)
}

func (c *Command) Help(ctx context.Context, r *readline.Readline) string {
return c.commandTree.Help(ctx, r)
}

// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------

func (c *Command) list(ctx context.Context, r *readline.Readline) error {
t := pterm.TreeNode{
Text: c.config.Filename() + ":",
}
{
child := pterm.TreeNode{
Text: "Environments:",
}
for _, request := range c.config.Environments() {
child.Children = append(child.Children, pterm.TreeNode{
Text: request,
})
}
if len(child.Children) > 0 {
t.Children = append(t.Children, child)
}
}
{
child := pterm.TreeNode{
Text: "Requests:",
}
for _, request := range c.config.Requests() {
child.Children = append(child.Children, pterm.TreeNode{
Text: request,
})
}
if len(child.Children) > 0 {
t.Children = append(t.Children, child)
}
}
return pterm.DefaultTree.WithRoot(t).Render()
}

func (c *Command) run(ctx context.Context, r *readline.Readline) error {
fs := r.FlagSets().Default()
args := []string{
"--env", r.Args().At(1),
}
if r.Args().LenGte(3) {
args = append(args, r.Args().From(2)...)
}
return shell.New(ctx, c.l, "bru", "run").
Args(args...).
Args(fs.Visited().Args()...).
Args(r.AdditionalArgs()...).
Dir(c.config.Filename()).
Run()
}

func (c *Command) env(ctx context.Context, r *readline.Readline) error {
if c.op == nil {
return errors.New("you must provide a one-password configuration")
}

envFilename := path.Join(c.config.Filename(), ".env")
templateFilename := path.Join(c.config.Filename(), "bruno.env")
if _, err := os.Stat(templateFilename); err != nil {
return err
}

c.l.Info("rendering secrets file:", envFilename)
return shell.New(ctx, c.l, "op", "inject", "-f", "-i", templateFilename, "-o", envFilename).Quiet().Run()
}

func (c *Command) open(ctx context.Context, r *readline.Readline) error {
prefFilename, err := gap.NewScope(gap.User, c.name).DataPath("preferences.json")
if err != nil {
return err
}
pref, err := NewPreferences(prefFilename)
if err != nil {
return err
}
if err := pref.AddLastOpenedCollection(c.config.Filename()); err != nil {
return err
}
if err := pref.Save(prefFilename); err != nil {
return err
}

return shell.New(ctx, c.l, "open", "-a", c.appName).Run()
}
Loading

0 comments on commit bec80f1

Please sign in to comment.