Skip to content

Commit

Permalink
replaying from api support
Browse files Browse the repository at this point in the history
now the hardest part
  • Loading branch information
chmouel committed Dec 18, 2023
1 parent 91414af commit 8818501
Show file tree
Hide file tree
Showing 24 changed files with 1,566 additions and 153 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/go-chi/chi/v5 v5.0.10
github.com/google/go-github/v57 v57.0.0
github.com/kr/pretty v0.3.1
github.com/mattn/go-isatty v0.0.20
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/mitchellh/mapstructure v1.5.0
Expand All @@ -20,7 +21,9 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/net v0.17.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -12,6 +13,10 @@ github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand All @@ -21,10 +26,13 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=
github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
121 changes: 17 additions & 104 deletions gosmee/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ func makeapp() *cli.App {
app := &cli.App{
Name: "gosmee",
Usage: "Forward SMEE url from an external endpoint to a local service",
UsageText: `gosmee can forward webhook from https://smee.io or from
itself to a local service. The server is the one from where the webhook
points to. The client runs on your laptop or behind a non publically
accessible endpoint and forward request to your local service`,
UsageText: `Gosmee can help you reroute webhooks either from https://smee.io or its own server to a local service.
Where the server is the source of the webhook, and the client, which you run on your laptop or behind a
non-publicly accessible endpoint, forward those requests to your local service.`,
EnableBashCompletion: true,
Version: strings.TrimSpace(string(Version)),
Commands: []*cli.Command{
Expand All @@ -36,19 +35,7 @@ accessible endpoint and forward request to your local service`,
Action: func(c *cli.Context) error {
return replay(c)
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "github-token",
Usage: "GitHub token to use to replay payloads",
Required: true,
Aliases: []string{"t"},
},
&cli.BoolFlag{
Name: "list-hooks",
Usage: "List hooks and its a IDs from a repository",
Aliases: []string{"L"},
},
},
Flags: append(commonFlags, replayFlags...),
},
{
Name: "server",
Expand All @@ -59,43 +46,7 @@ accessible endpoint and forward request to your local service`,
}
return serve(c)
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "public-url",
Usage: "Public URL to show to user, useful when you are behind a proxy.",
},
&cli.IntFlag{
Name: "port",
Aliases: []string{"p"},
Value: defaultServerPort,
Usage: "Port to listen on",
},
&cli.BoolFlag{
Name: "auto-cert",
Value: false,
Usage: "Automatically generate letsencrypt certs",
},
&cli.StringFlag{
Name: "footer",
Usage: "An HTML string to show in footer for copyright and author",
},
&cli.StringFlag{
Name: "address",
Aliases: []string{"a"},
Value: defaultServerAddress,
Usage: "Address to listen on",
},
&cli.StringFlag{
Name: "tls-cert",
Usage: "TLS certificate file",
EnvVars: []string{"GOSMEE_TLS_CERT"},
},
&cli.StringFlag{
Name: "tls-key",
Usage: "TLS key file",
EnvVars: []string{"GOSMEE_TLS_KEY"},
},
},
Flags: serverFlags,
},
{
Name: "client",
Expand Down Expand Up @@ -129,60 +80,22 @@ accessible endpoint and forward request to your local service`,
decorate = false
}
cfg := goSmee{
smeeURL: smeeURL,
targetURL: targetURL,
saveDir: c.String("saveDir"),
noReplay: c.Bool("noReplay"),
decorate: decorate,
ignoreEvents: c.StringSlice("ignore-event"),
channel: c.String("channel"),
targetCnxTimeout: c.Int("target-connection-timeout"),
insecureTLSVerify: c.Bool("insecure-skip-tls-verify"),
replayDataOpts: &replayDataOpts{
smeeURL: smeeURL,
targetURL: targetURL,
saveDir: c.String("saveDir"),
noReplay: c.Bool("noReplay"),
decorate: decorate,
ignoreEvents: c.StringSlice("ignore-event"),
targetCnxTimeout: c.Int("target-connection-timeout"),
insecureTLSVerify: c.Bool("insecure-skip-tls-verify"),
},
channel: c.String("channel"),
}
err := cfg.clientSetup()
return err
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "channel",
Aliases: []string{"c"},
Usage: "gosmee channel to listen, only useful when you are not use smee.io",
Value: smeeChannel,
},
&cli.StringSliceFlag{
Name: "ignore-event",
Aliases: []string{"I"},
Usage: "Ignore these events",
},
&cli.StringFlag{
Name: "saveDir",
Usage: "Save payloads to `DIR` populated with shell scripts to replay easily.",
Aliases: []string{"s"},
EnvVars: []string{"GOSMEE_SAVEDIR"},
},
&cli.IntFlag{
Name: "target-connection-timeout",
Usage: "How long to wait when forwarding the request to the service",
EnvVars: []string{"GOSMEE_TARGET_TIMEOUT"},
Value: defaultTimeout,
},
&cli.BoolFlag{
Name: "noReplay",
Usage: "Do not replay payloads",
Aliases: []string{"n"},
Value: false,
},
&cli.BoolFlag{
Name: "nocolor",
Usage: "Disable color output, automatically disabled when non tty",
EnvVars: []string{"NO_COLOR"},
},
&cli.BoolFlag{
Name: "insecure-skip-tls-verify",
Value: false,
Usage: "If true, the target server's certificate will not be checked for validity. This will make your HTTPS connections insecure",
},
},
Flags: append(commonFlags, clientFlags...),
},
{
Name: "completion",
Expand Down
70 changes: 37 additions & 33 deletions gosmee/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ const smeeChannel = "messages"
const tsFormat = "2006-01-02T15.04.01.000"

type goSmee struct {
saveDir, smeeURL, targetURL string
decorate, noReplay bool
ignoreEvents []string
channel string
targetCnxTimeout int
insecureTLSVerify bool
replayDataOpts *replayDataOpts
channel string
}

type payloadMsg struct {
Expand Down Expand Up @@ -144,10 +140,10 @@ func (c goSmee) parse(now time.Time, data []byte) (payloadMsg, error) {

pm.timestamp = dt.Format(tsFormat)

if len(c.ignoreEvents) > 0 && pm.eventType != "" {
for _, v := range c.ignoreEvents {
if len(c.replayDataOpts.ignoreEvents) > 0 && pm.eventType != "" {
for _, v := range c.replayDataOpts.ignoreEvents {
if v == pm.eventType {
fmt.Fprintf(os.Stdout, "%sskipping event %s as requested\n", c.emoji("!", "blue+b"), pm.eventType)
fmt.Fprintf(os.Stdout, "%sskipping event %s as requested\n", emoji("!", "blue+b", c.replayDataOpts.decorate), pm.eventType)
return payloadMsg{}, nil
}
}
Expand All @@ -156,17 +152,17 @@ func (c goSmee) parse(now time.Time, data []byte) (payloadMsg, error) {
return pm, nil
}

func (c goSmee) emoji(emoji, color string) string {
if !c.decorate {
func emoji(emoji, color string, decorate bool) string {
if !decorate {
return ""
}
return ansi.Color(emoji, color) + " "
}

func (c goSmee) saveData(pm payloadMsg) error {
// check if saveDir is created
if _, err := os.Stat(c.saveDir); os.IsNotExist(err) {
if err := os.MkdirAll(c.saveDir, 0o755); err != nil {
if _, err := os.Stat(c.replayDataOpts.saveDir); os.IsNotExist(err) {
if err := os.MkdirAll(c.replayDataOpts.saveDir, 0o755); err != nil {
return err
}
}
Expand All @@ -178,7 +174,7 @@ func (c goSmee) saveData(pm payloadMsg) error {
fbasepath = pm.timestamp
}

jsonfile := fmt.Sprintf("%s/%s.json", c.saveDir, fbasepath)
jsonfile := fmt.Sprintf("%s/%s.json", c.replayDataOpts.saveDir, fbasepath)
f, err := os.Create(jsonfile)
if err != nil {
return err
Expand All @@ -190,9 +186,9 @@ func (c goSmee) saveData(pm payloadMsg) error {
return err
}

shscript := fmt.Sprintf("%s/%s.sh", c.saveDir, fbasepath)
shscript := fmt.Sprintf("%s/%s.sh", c.replayDataOpts.saveDir, fbasepath)

fmt.Fprintf(os.Stdout, "%s%s and %s has been saved\n", c.emoji("⌁", "yellow+b"), shscript, jsonfile)
fmt.Fprintf(os.Stdout, "%s%s and %s has been saved\n", emoji("⌁", "yellow+b", c.replayDataOpts.decorate), shscript, jsonfile)
s, err := os.Create(shscript)
if err != nil {
return err
Expand All @@ -212,7 +208,7 @@ func (c goSmee) saveData(pm payloadMsg) error {
FileBase string
}{
Headers: headers,
TargetURL: c.targetURL,
TargetURL: c.replayDataOpts.targetURL,
ContentType: pm.contentType,
FileBase: fbasepath,
}); err != nil {
Expand All @@ -223,12 +219,20 @@ func (c goSmee) saveData(pm payloadMsg) error {
return os.Chmod(shscript, 0o755)
}

func (c goSmee) replayData(pm payloadMsg) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.targetCnxTimeout)*time.Second)
type replayDataOpts struct {
insecureTLSVerify bool
targetCnxTimeout int
decorate, noReplay bool
saveDir, smeeURL, targetURL string
ignoreEvents []string
}

func replayData(ropts *replayDataOpts, pm payloadMsg) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(ropts.targetCnxTimeout)*time.Second)
defer cancel()
//nolint: gosec
client := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !c.insecureTLSVerify}}}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.targetURL, strings.NewReader(string(pm.body)))
client := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !ropts.insecureTLSVerify}}}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, ropts.targetURL, strings.NewReader(string(pm.body)))
if err != nil {
return err
}
Expand Down Expand Up @@ -256,23 +260,23 @@ func (c goSmee) replayData(pm payloadMsg) error {
msg = fmt.Sprintf("%s %s", pm.eventID, msg)
}

msg = fmt.Sprintf("%s %s replayed to %s, status: %s", pm.timestamp, msg, ansi.Color(c.targetURL, "green+ub"), ansi.Color(fmt.Sprintf("%d", resp.StatusCode), "blue+b"))
msg = fmt.Sprintf("%s %s replayed to %s, status: %s", pm.timestamp, msg, ansi.Color(ropts.targetURL, "green+ub"), ansi.Color(fmt.Sprintf("%d", resp.StatusCode), "blue+b"))
if resp.StatusCode > 299 {
msg = fmt.Sprintf("%s, error: %s", msg, resp.Status)
}
fmt.Fprintf(os.Stdout, "%s%s\n", c.emoji("•", "magenta+b"), msg)
fmt.Fprintf(os.Stdout, "%s%s\n", emoji("•", "magenta+b", ropts.decorate), msg)
return nil
}

func (c goSmee) clientSetup() error {
version := strings.TrimSpace(string(Version))
fmt.Fprintf(os.Stdout, "%sStarting gosmee version: %s\n", c.emoji("⇉", "green+b"), version)
client := sse.NewClient(c.smeeURL, sse.ClientMaxBufferSize(1<<20))
fmt.Fprintf(os.Stdout, "%sStarting gosmee version: %s\n", emoji("⇉", "green+b", c.replayDataOpts.decorate), version)
client := sse.NewClient(c.replayDataOpts.smeeURL, sse.ClientMaxBufferSize(1<<20))
client.Headers["User-Agent"] = fmt.Sprintf("gosmee/%s", version)
// this is to get nginx to work
client.Headers["X-Accel-Buffering"] = "no"
channel := filepath.Base(c.smeeURL)
if strings.HasPrefix(c.smeeURL, "https://smee.io") {
channel := filepath.Base(c.replayDataOpts.smeeURL)
if strings.HasPrefix(c.replayDataOpts.smeeURL, "https://smee.io") {
channel = smeeChannel
}
err := client.Subscribe(channel, func(msg *sse.Event) {
Expand All @@ -283,9 +287,9 @@ func (c goSmee) clientSetup() error {
fmt.Fprintf(os.Stdout,
"%s %sForwarding %s to %s\n",
nowStr,
c.emoji("✓", "yellow+b"),
ansi.Color(c.smeeURL, "green+u"),
ansi.Color(c.targetURL, "green+u"))
emoji("✓", "yellow+b", c.replayDataOpts.decorate),
ansi.Color(c.replayDataOpts.smeeURL, "green+u"),
ansi.Color(c.replayDataOpts.targetURL, "green+u"))
return
}

Expand Down Expand Up @@ -318,7 +322,7 @@ func (c goSmee) clientSetup() error {
}

if string(msg.Data) != "{}" {
if c.saveDir != "" {
if c.replayDataOpts.saveDir != "" {
err := c.saveData(pm)
if err != nil {
fmt.Fprintf(os.Stdout,
Expand All @@ -330,8 +334,8 @@ func (c goSmee) clientSetup() error {
return
}
}
if !c.noReplay {
if err := c.replayData(pm); err != nil {
if !c.replayDataOpts.noReplay {
if err := replayData(c.replayDataOpts, pm); err != nil {
fmt.Fprintf(os.Stdout,
"%s %s forwarding message with headers '%s' - %s\n",
nowStr,
Expand Down
Loading

0 comments on commit 8818501

Please sign in to comment.