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

Allow predefining targets at startup #16

Merged
merged 8 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 76 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
run:
# default concurrency is a available CPU number
concurrency: 4

# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 5m

# exit code when at least one issue was found, default is 1
issues-exit-code: 1

# include test files or not, default is true
tests: true

linters-settings:
lll:
line-length: 250
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
govet:
check-shadowing: false

nolintlint:
allow-leading-space: false
allow-unused: false # report any unused nolint directives
require-specific: true # don't require nolint directives to be specific about which linter is being skipped

maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exhaustive
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- maligned
- misspell
- nakedret
- noctx
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- wsl
29 changes: 18 additions & 11 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
ldflags:
- -s -w -X github.com/rb3ckers/trafficmirror.Version={{.Version}}
- -X github.com/rb3ckers/trafficmirror.Commit={{.Commit}}
- -X github.com/rb3ckers/trafficmirror.Date={{.Date}}
checksum:
name_template: checksums.txt
snapshot:
Expand All @@ -23,14 +27,17 @@ changelog:
- "^docs:"
- "^test:"
dockers:
- extra_files:
- datatypes/
- go.mod
- go.sum
- main.go
- rootfs/
image_templates:
- docker.io/stackstate/trafficmirror:latest
- docker.io/stackstate/trafficmirror:{{ .Tag }}
- docker.io/stackstate/trafficmirror:v{{ .Major }}
- docker.io/stackstate/trafficmirror:v{{ .Major }}.{{ .Minor }}
-
goos: linux
goarch: amd64
dockerfile: Dockerfile.goreleaser
build_flag_templates:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
image_templates:
- quay.io/stackstate/trafficmirror:latest
- quay.io/stackstate/trafficmirror:{{ .Tag }}
- quay.io/stackstate/trafficmirror:v{{ .Major }}
- quay.io/stackstate/trafficmirror:v{{ .Major }}.{{ .Minor }}
6 changes: 6 additions & 0 deletions Dockerfile.goreleaser
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY trafficmirror .
USER nonroot:nonroot

ENTRYPOINT ["/trafficmirror"]
152 changes: 152 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package cmd

import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"

"github.com/hierynomus/taipan"
home "github.com/mitchellh/go-homedir"
"github.com/rb3ckers/trafficmirror/internal/config"
"github.com/rb3ckers/trafficmirror/internal/proxy"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
)

var (
Version string
Commit string
Date string
)

var EnvPrefix = "TRAFFICMIRROR_"

func RootCommand(cfg *config.Config) *cobra.Command {
var verbosity int

cmd := &cobra.Command{
Use: "trafficmirror",
Short: "Runs Traffic Mirror",
Long: `
HTTP proxy that:
* sends requests to a main endpoint from which the response is returned
* can mirror the requests to any additional number of endpoints

Additional targets are configured via PUT/DELETE on the '/targets?url=<endpoint>'.
`,
Version: fmt.Sprintf("%s (Built on: %s, Commit: %s)", Version, Date, Commit),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
switch verbosity {
case 0:
// Nothing to do
case 1:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case 2: //nolint:gomnd
zerolog.SetGlobalLevel(zerolog.DebugLevel)
default:
zerolog.SetGlobalLevel(zerolog.TraceLevel)
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
PrintUsage(cfg)
if err := RunProxy(cfg); err != nil {
return err
}

return nil
},
}

cmd.PersistentFlags().CountVarP(&verbosity, "verbose", "v", "Print more verbose logging")

cmd.Flags().StringP("listen", "l", ":8080", "Address to listen on and mirror traffic from")
cmd.Flags().StringP("main", "m", "http://localhost:8888", "Main proxy target, its responses will be returned to the client")
cmd.Flags().String("targets", "targets", "Path on which additional targets to mirror to can be added/deleted/listed via PUT, DELETE and GET")
cmd.Flags().String("targets-address", "", "Address on which the targets endpoint is made available. Leave empty to expose it on the address that is being mirrored")
cmd.Flags().String("username", "", "Username to protect the configuration 'targets' endpoint with.")
cmd.Flags().String("password", "", "Password to protect the configuration 'targets' endpoint with.")
cmd.Flags().String("passwordFile", "", "Provide a file that contains username/password to protect the configuration 'targets' endpoint. Contains 1 username/password combination separated by ':'.")
cmd.Flags().Int("fail-after", 30, "Remove a target when it has been failing for this many minutes.") //nolint:gomnd
cmd.Flags().Int("retry-after", 1, "After 5 successive failures a target is temporarily disabled, it will be retried after this many minutes.")
cmd.Flags().StringSlice("mirror", []string{}, "Start with mirroring traffic to provided targets")

return cmd
}

func RunProxy(cfg *config.Config) error {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)

signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

p := proxy.NewProxy(cfg)

go func() {
sig := <-sigs
log.Printf("Received signal '%s', exiting\n", sig)

if err := p.Stop(); err != nil {
panic(err)
}

done <- true
}()

if err := p.Start(context.Background()); err != nil {
return err
}

<-done

return nil
}

func Execute(ctx context.Context) {
cfg := &config.Config{}
cmd := RootCommand(cfg)

homeFolder, err := home.Expand("~/.trafficmirror")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
}

zerolog.SetGlobalLevel(zerolog.ErrorLevel)

taipanConfig := &taipan.Config{
DefaultConfigName: "trafficmirror",
ConfigurationPaths: []string{".", homeFolder},
EnvironmentPrefix: EnvPrefix,
AddConfigFlag: true,
ConfigObject: cfg,
PrefixCommands: true,
}

t := taipan.New(taipanConfig)
t.Inject(cmd)

if err := cmd.ExecuteContext(ctx); err != nil {
fmt.Printf("🎃 %s\n", err)
os.Exit(1)
}
}

func PrintUsage(cfg *config.Config) {
var targetsText string
if cfg.TargetsListenAddress != "" {
targetsText = fmt.Sprintf("http://%s/%s", cfg.TargetsListenAddress, cfg.TargetsEndpoint)
} else {
targetsText = fmt.Sprintf("http://%s/%s", cfg.ListenAddress, cfg.TargetsEndpoint)
}

fmt.Printf("Add/remove/list mirror targets via PUT/DELETE/GET at %s:\n", targetsText)
fmt.Printf("List : curl %s\n", targetsText)
fmt.Printf("Add : curl -X PUT %s?url=http://localhost:5678\n", targetsText)
fmt.Printf("Remove: curl -X DELETE %s?url=http://localhost:5678\n", targetsText)
fmt.Println()
}
Loading