diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/controller/controller.go index c6d980dee..925c3f28e 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/controller/controller.go @@ -21,6 +21,8 @@ import ( var _ cli.Command = (*Command)(nil) var _ cli.CommandAutocomplete = (*Command)(nil) +var devOnlyControllerFlags = func(*Command, *base.FlagSet) {} + type Command struct { *base.Server @@ -37,12 +39,13 @@ type Command struct { flagConfig string flagLogLevel string flagLogFormat string + flagCombineLogs bool flagDev bool flagDevAdminPassword string flagDevControllerAPIListenAddr string flagDevControllerClusterListenAddr string flagDevOrgId string - flagCombineLogs bool + flagDevPassthroughDirectory string } func (c *Command) Synopsis() string { @@ -106,19 +109,17 @@ func (c *Command) Flags() *base.FlagSets { }) f.StringVar(&base.StringVar{ - Name: "dev-org-id", - Target: &c.flagDevOrgId, - Default: "", - EnvVar: "WATCHTWER_DEV_ORG_ID", + Name: "dev-org-id", + Target: &c.flagDevOrgId, + EnvVar: "WATCHTWER_DEV_ORG_ID", Usage: "Auto-created organization ID. This only applies when running in \"dev\" " + "mode.", }) f.StringVar(&base.StringVar{ - Name: "dev-admin-password", - Target: &c.flagDevAdminPassword, - Default: "", - EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD", + Name: "dev-admin-password", + Target: &c.flagDevAdminPassword, + EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD", Usage: "Initial admin password. This only applies when running in \"dev\" " + "mode.", }) @@ -138,12 +139,13 @@ func (c *Command) Flags() *base.FlagSets { }) f.BoolVar(&base.BoolVar{ - Name: "combine-logs", - Target: &c.flagCombineLogs, - Default: false, - Usage: "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr.", + Name: "combine-logs", + Target: &c.flagCombineLogs, + Usage: "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr.", }) + devOnlyControllerFlags(c, f) + return set } @@ -315,6 +317,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { if c.flagDevOrgId != "" { c.Config.DefaultOrgId = c.flagDevOrgId } + c.Config.PassthroughDirectory = c.flagDevPassthroughDirectory for _, l := range c.Config.Listeners { if len(l.Purpose) != 1 { diff --git a/internal/cmd/commands/controller/dev_flags.go b/internal/cmd/commands/controller/dev_flags.go new file mode 100644 index 000000000..5c43524fb --- /dev/null +++ b/internal/cmd/commands/controller/dev_flags.go @@ -0,0 +1,18 @@ +// +build dev + +package controller + +import "github.com/hashicorp/watchtower/internal/cmd/base" + +func init() { + devOnlyControllerFlags = addDevOnlyControllerFlags +} + +func addDevOnlyControllerFlags(c *Command, f *base.FlagSet) { + f.StringVar(&base.StringVar{ + Name: "dev-passthrough-directory", + Target: &c.flagDevPassthroughDirectory, + EnvVar: "WATCHTOWER_DEV_PASSTHROUGH_DIRECTORY", + Usage: "Enables a passthrough directory in the webserver at /passthrough", + }) +} diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 54f4fe7a4..8c10b7640 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -17,6 +17,8 @@ import ( var _ cli.Command = (*Command)(nil) var _ cli.CommandAutocomplete = (*Command)(nil) +var devOnlyControllerFlags = func(*Command, *base.FlagSet) {} + type Command struct { *base.Server @@ -30,12 +32,13 @@ type Command struct { flagConfig string flagLogLevel string flagLogFormat string + flagCombineLogs bool flagDev bool flagDevAdminPassword string flagDevOrgId string flagDevControllerAPIListenAddr string flagDevControllerClusterListenAddr string - flagCombineLogs bool + flagDevPassthroughDirectory string } func (c *Command) Synopsis() string { @@ -82,19 +85,17 @@ func (c *Command) Flags() *base.FlagSets { f = set.NewFlagSet("Dev Options") f.StringVar(&base.StringVar{ - Name: "dev-org-id", - Target: &c.flagDevOrgId, - Default: "", - EnvVar: "WATCHTWER_DEV_ORG_ID", + Name: "dev-org-id", + Target: &c.flagDevOrgId, + EnvVar: "WATCHTWER_DEV_ORG_ID", Usage: "Auto-created organization ID. This only applies when running in \"dev\" " + "mode.", }) f.StringVar(&base.StringVar{ - Name: "dev-admin-password", - Target: &c.flagDevAdminPassword, - Default: "", - EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD", + Name: "dev-admin-password", + Target: &c.flagDevAdminPassword, + EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD", Usage: "Initial admin password. This only applies when running in \"dev\" " + "mode.", }) @@ -114,12 +115,13 @@ func (c *Command) Flags() *base.FlagSets { }) f.BoolVar(&base.BoolVar{ - Name: "combine-logs", - Target: &c.flagCombineLogs, - Default: false, - Usage: "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr.", + Name: "combine-logs", + Target: &c.flagCombineLogs, + Usage: "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr.", }) + devOnlyControllerFlags(c, f) + return set } @@ -153,6 +155,7 @@ func (c *Command) Run(args []string) int { if c.flagDevOrgId != "" { devConfig.DefaultOrgId = c.flagDevOrgId } + devConfig.PassthroughDirectory = c.flagDevPassthroughDirectory for _, l := range devConfig.Listeners { if len(l.Purpose) != 1 { diff --git a/internal/cmd/commands/dev/dev_flags.go b/internal/cmd/commands/dev/dev_flags.go new file mode 100644 index 000000000..9cb3d3021 --- /dev/null +++ b/internal/cmd/commands/dev/dev_flags.go @@ -0,0 +1,18 @@ +// +build dev + +package dev + +import "github.com/hashicorp/watchtower/internal/cmd/base" + +func init() { + devOnlyControllerFlags = addDevOnlyControllerFlags +} + +func addDevOnlyControllerFlags(c *Command, f *base.FlagSet) { + f.StringVar(&base.StringVar{ + Name: "dev-passthrough-directory", + Target: &c.flagDevPassthroughDirectory, + EnvVar: "WATCHTOWER_DEV_PASSTHROUGH_DIRECTORY", + Usage: "Enables a passthrough directory in the webserver at /passthrough", + }) +} diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index ade410ca0..01d915293 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -65,8 +65,9 @@ listener "tcp" { type Config struct { *configutil.SharedConfig `hcl:"-"` - DevController bool `hcl:"-"` - DefaultOrgId string `hcl:"default_org_id"` + DevController bool `hcl:"-"` + DefaultOrgId string `hcl:"default_org_id"` + PassthroughDirectory string `hcl:"-"` } // DevWorker is a Config that is used for dev mode of Watchtower diff --git a/internal/servers/controller/config.go b/internal/servers/controller/config.go index 1a7f4823a..bbaa06168 100644 --- a/internal/servers/controller/config.go +++ b/internal/servers/controller/config.go @@ -9,6 +9,6 @@ import ( type Config struct { *base.Server - RawConfig *config.Config - BaseContext context.Context + RawConfig *config.Config + BaseContext context.Context } diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 9511ed9b5..8998c1f47 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "path" + "path/filepath" "strings" "time" @@ -30,6 +31,20 @@ func (c *Controller) handler(props HandlerProperties) http.Handler { // Create the muxer to handle the actual endpoints mux := http.NewServeMux() + if c.conf.RawConfig.PassthroughDirectory != "" { + // Panic may not be ideal but this is never a production call and it'll + // panic on startup. We could also just change the function to return + // an error. + abs, err := filepath.Abs(c.conf.RawConfig.PassthroughDirectory) + if err != nil { + panic(err) + } + c.logger.Warn("serving passthrough files at /passthrough", "path", abs) + fs := http.FileServer(http.Dir(abs)) + prefixHandler := http.StripPrefix("/passthrough/", fs) + mux.Handle("/passthrough/", prefixHandler) + } + mux.Handle("/v1/", handleGrpcGateway(c)) corsWrappedHandler := wrapHandlerWithCors(mux, props)