-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding commands for configuration management and init
- Loading branch information
Showing
4 changed files
with
387 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
"github.com/scalescape/dolores/client" | ||
"github.com/scalescape/dolores/secrets" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
type ConfigCommand struct { | ||
*cli.Command | ||
rcli func(context.Context) *client.Client | ||
log zerolog.Logger | ||
} | ||
|
||
func NewConfig(client func(context.Context) *client.Client) *ConfigCommand { | ||
log := log.With().Str("cmd", "config").Logger() | ||
cmd := &cli.Command{ | ||
Name: "config", | ||
Usage: "secrets management", | ||
Flags: []cli.Flag{}, | ||
} | ||
cfg := &ConfigCommand{ | ||
Command: cmd, | ||
log: log, | ||
rcli: client, | ||
} | ||
cfg.Subcommands = append(cfg.Subcommands, EncryptCommand(cfg.encryptAction)) | ||
cfg.Subcommands = append(cfg.Subcommands, DecryptCommand(cfg.decryptAction)) | ||
cfg.Subcommands = append(cfg.Subcommands, EditCommand(cfg.editAction)) | ||
return cfg | ||
} | ||
|
||
func (c *ConfigCommand) editAction(ctx *cli.Context) error { | ||
env := ctx.String("environment") | ||
log := c.log.With().Str("cmd", "config.edit").Str("environment", env).Logger() | ||
dcfg, err := parseDecryptConfig(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
sec := secrets.NewSecertsManager(log, c.rcli(ctx.Context)) | ||
cfg := secrets.EditConfig{DecryptConfig: dcfg} | ||
if err := sec.Edit(cfg); err != nil { | ||
log.Error().Msgf("error editing file: %v", err) | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (c *ConfigCommand) encryptAction(ctx *cli.Context) error { | ||
env := ctx.String("environment") | ||
file := ctx.String("file") | ||
name := ctx.String("name") | ||
log := c.log.With().Str("cmd", "config.encrypt").Str("environment", env).Logger() | ||
secMan := secrets.NewSecertsManager(log, c.rcli(ctx.Context)) | ||
req := secrets.EncryptConfig{Environment: env, FileName: file, Name: name} | ||
if err := secMan.Encrypt(req); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func parseDecryptConfig(ctx *cli.Context) (secrets.DecryptConfig, error) { | ||
env := ctx.String("environment") | ||
name := ctx.String("name") | ||
req := secrets.DecryptConfig{ | ||
Environment: env, | ||
Name: name, | ||
Out: os.Stdout, | ||
} | ||
parseKeyConfig(ctx, &req) | ||
if err := req.Valid(); err != nil { | ||
return secrets.DecryptConfig{}, fmt.Errorf("pass appropriate key or key-file to decrypt: %w", err) | ||
} | ||
|
||
return req, nil | ||
} | ||
|
||
func parseKeyConfig(ctx *cli.Context, cfg *secrets.DecryptConfig) { | ||
log.Trace().Msgf("parsing configuration required to decrypt config") | ||
key := ctx.String("key") | ||
keyFile := ctx.String("key-file") | ||
if keyFile == "" { | ||
keyFile = os.Getenv("DOLORES_SECRETS_KEY_FILE") | ||
} | ||
if key == "" { | ||
key = os.Getenv("DOLORES_SECRETS_KEY") | ||
} | ||
cfg.KeyFile = keyFile | ||
cfg.Key = key | ||
} | ||
|
||
func (c *ConfigCommand) decryptAction(ctx *cli.Context) error { | ||
req, err := parseDecryptConfig(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
log := c.log.With().Str("cmd", "config.dencrypt").Str("environment", req.Environment).Logger() | ||
log.Trace().Str("cmd", "config.decrypt").Msgf("running decryption") | ||
sec := secrets.NewSecertsManager(log, c.rcli(ctx.Context)) | ||
return sec.Decrypt(req) | ||
} | ||
|
||
func EncryptCommand(action cli.ActionFunc) *cli.Command { | ||
return &cli.Command{ | ||
Name: "encrypt", | ||
Usage: "encrypt the passed file", | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "file", | ||
Aliases: []string{"f"}, | ||
Required: true, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "name", | ||
Required: true, | ||
}, | ||
}, | ||
Action: action, | ||
} | ||
} | ||
|
||
func DecryptCommand(action cli.ActionFunc) *cli.Command { | ||
return &cli.Command{ | ||
Name: "decrypt", | ||
Usage: "decrypt the remote configuration", | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "name", | ||
Required: true, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "key", | ||
Aliases: []string{"k"}, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "key-file", | ||
}, | ||
}, | ||
Action: action, | ||
} | ||
} | ||
|
||
func EditCommand(action cli.ActionFunc) *cli.Command { | ||
return &cli.Command{ | ||
Name: "edit", | ||
Usage: "edit the secrets configuration", | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "name", | ||
Required: true, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "key", | ||
Aliases: []string{"k"}, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "key-file", | ||
}, | ||
}, | ||
Action: action, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
"github.com/scalescape/dolores/client" | ||
"github.com/scalescape/dolores/config" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
type InitCommand struct { | ||
*cli.Command | ||
log zerolog.Logger | ||
rcli func(context.Context) *client.Client | ||
} | ||
|
||
type GetClient func(context.Context) *client.Client | ||
|
||
func NewInitCommand(newCli GetClient) *cli.Command { | ||
ic := &InitCommand{ | ||
log: log.With().Str("cmd", "init").Logger(), | ||
rcli: newCli, | ||
} | ||
cmd := &cli.Command{ | ||
Name: "init", | ||
Usage: "bootstrap with settings", | ||
Action: ic.initialize, | ||
} | ||
return cmd | ||
} | ||
|
||
type Input struct { | ||
Bucket string | ||
Environment string | ||
Location string | ||
ApplicationCredentials string `survey:"google_creds"` | ||
} | ||
|
||
func (c *InitCommand) getData() (*Input, error) { | ||
credFile := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") | ||
qs := []*survey.Question{ | ||
{ | ||
Name: "environment", | ||
Prompt: &survey.Select{ | ||
Message: "Choose an environment:", | ||
Options: []string{"production", "staging"}, | ||
Default: "production", | ||
}, | ||
}, | ||
{ | ||
Name: "bucket", | ||
Prompt: &survey.Input{ | ||
Message: "Enter the GCS bucket name:", | ||
}, | ||
Validate: survey.Required, | ||
}, | ||
{ | ||
Name: "location", | ||
Validate: survey.Required, | ||
Prompt: &survey.Input{ | ||
Message: "Enter the object location to store the secrets", | ||
}, | ||
}, | ||
} | ||
if credFile != "" { | ||
qs = append(qs, &survey.Question{ | ||
Name: "google_creds", | ||
Validate: survey.Required, | ||
Prompt: &survey.Select{ | ||
Message: "Confirm using GOOGLE_APPLICATION_CREDENTIALS env as credentials file", | ||
Options: []string{credFile, ""}, | ||
}, | ||
}) | ||
} else { | ||
qs = append(qs, &survey.Question{ | ||
Name: "google_creds", | ||
Prompt: &survey.Input{ | ||
Message: "Enter google service account file path", | ||
}, | ||
Validate: survey.Required, | ||
}) | ||
} | ||
res := new(Input) | ||
if err := survey.Ask(qs, res); err != nil { | ||
return nil, err | ||
} | ||
return res, nil | ||
} | ||
|
||
func (c *InitCommand) initialize(ctx *cli.Context) error { | ||
inp, err := c.getData() | ||
if err != nil { | ||
return err | ||
} | ||
md := config.Metadata{ | ||
Bucket: inp.Bucket, | ||
Locations: map[string]string{ | ||
inp.Environment: inp.Location, | ||
}, | ||
} | ||
if err := c.rcli(ctx.Context).SaveMetadata(ctx.Context, inp.Bucket, md); err != nil { | ||
c.log.Error().Msgf("error writing metadta: %v", err) | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
"github.com/scalescape/dolores/client" | ||
"github.com/scalescape/dolores/config" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
var ( | ||
version = "0.0.1" | ||
sha = "undefined" | ||
) | ||
|
||
func main() { | ||
log.Logger = log.Output(zerolog.NewConsoleWriter()) | ||
cli.VersionPrinter = VersionDisplay | ||
|
||
app := &cli.App{ | ||
Name: "Dolores", | ||
Usage: "service configuration management with your own cloud platform", | ||
Version: version, | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "environment", Aliases: []string{"env"}, | ||
Usage: "environment where you want to manage [staging|production]", | ||
}, | ||
}, | ||
Commands: []*cli.Command{ | ||
NewConfig(newClient).Command, | ||
NewInitCommand(newClient), | ||
}, | ||
} | ||
if err := app.Run(os.Args); err != nil { | ||
log.Fatal().Msgf("error: %v", err) | ||
} | ||
} | ||
|
||
func VersionDisplay(cc *cli.Context) { | ||
fmt.Printf("rom %s (%s)", version, sha) //nolint | ||
} | ||
|
||
func newClient(ctx context.Context) *client.Client { | ||
cfg, err := config.LoadClient() | ||
if err != nil { | ||
log.Fatal().Msgf("error loading config: %v", err) | ||
return nil | ||
} | ||
client, err := client.New(ctx, cfg) | ||
if err != nil { | ||
log.Fatal().Msgf("error building client: %v", err) | ||
return nil | ||
} | ||
return client | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package config | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/kelseyhightower/envconfig" | ||
) | ||
|
||
var ErrInvalidGoogleCreds = errors.New("invalid google application credentials") | ||
|
||
type Google struct { | ||
ApplicationCredentials string `split_words:"true"` | ||
StorageBucket string `split_words:"true" required:"true"` | ||
} | ||
|
||
type Metadata struct { | ||
Bucket string `json:"bucket"` | ||
Locations map[string]string `json:"locations"` | ||
CreatedAt time.Time `json:"created_at"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
} | ||
|
||
type Client struct { | ||
} | ||
|
||
func (c Client) BucketName() string { | ||
return c.Google.StorageBucket | ||
} | ||
|
||
func (c Client) Valid() error { | ||
if c.Google.ApplicationCredentials == "" { | ||
return ErrInvalidGoogleCreds | ||
} | ||
return nil | ||
} | ||
|
||
func LoadClient() (Client, error) { | ||
var cfg Client | ||
if err := envconfig.Process("GOOGLE", &cfg.Google); err != nil { | ||
return Client{}, fmt.Errorf("processing config: %w", err) | ||
} | ||
if err := cfg.Valid(); err != nil { | ||
return Client{}, err | ||
} | ||
return cfg, nil | ||
} |