From 374a3991a2a4c602a77324d1cc05541db80ebe03 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 21 May 2020 16:12:31 -0400 Subject: [PATCH] Add a passthrough directory feature. (#76) This is only compiled in when a `dev` build tag is specified (see #75) for safety. Attempting to use the flag without that tag at build time will fail. However, it is always available within the codebase itself so that tests can be written against this functionality in a programmatic way. Manual testing was performed; no plans to add automated ones as this isn't production-necessary nor exported functionality. --- .../cmd/commands/controller/controller.go | 29 ++++++++++--------- internal/cmd/commands/controller/dev_flags.go | 18 ++++++++++++ internal/cmd/commands/dev/dev.go | 29 ++++++++++--------- internal/cmd/commands/dev/dev_flags.go | 18 ++++++++++++ internal/cmd/config/config.go | 5 ++-- internal/servers/controller/config.go | 4 +-- internal/servers/controller/handler.go | 15 ++++++++++ 7 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 internal/cmd/commands/controller/dev_flags.go create mode 100644 internal/cmd/commands/dev/dev_flags.go 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)