cmd/tailscale: add background mode to serve/funnel wip (#9202)
> **Note** > Behind the `TAILSCALE_FUNNEL_DEV` flag * Expose additional listeners through flags * Add a --bg flag to run in the background * --set-path to set a path for a specific target (assumes running in background) See the parent issue for more context. Updates #8489 Signed-off-by: Tyler Smalley <tyler@tailscale.com>main
parent
5ee349e075
commit
70a9854b39
@ -0,0 +1,848 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"os" |
||||
"path/filepath" |
||||
"reflect" |
||||
"runtime" |
||||
"testing" |
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli" |
||||
"tailscale.com/ipn" |
||||
"tailscale.com/types/logger" |
||||
) |
||||
|
||||
func TestServeDevConfigMutations(t *testing.T) { |
||||
// Stateful mutations, starting from an empty config.
|
||||
type step struct { |
||||
command []string // serve args; nil means no command to run (only reset)
|
||||
reset bool // if true, reset all ServeConfig state
|
||||
want *ipn.ServeConfig // non-nil means we want a save of this value
|
||||
wantErr func(error) (badErrMsg string) // nil means no error is wanted
|
||||
line int // line number of addStep call, for error messages
|
||||
|
||||
debugBreak func() |
||||
} |
||||
var steps []step |
||||
add := func(s step) { |
||||
_, _, s.line, _ = runtime.Caller(1) |
||||
steps = append(steps, s) |
||||
} |
||||
|
||||
// using port number
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("funnel --bg 3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, |
||||
}, |
||||
}) |
||||
|
||||
// funnel background
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("funnel --bg localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, |
||||
}, |
||||
}) |
||||
|
||||
// serve background
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --bg localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// --set-path runs in background
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --set-path=/ localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// using http listener
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --bg --http=80 localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// using https listener with a valid port
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --bg --https=8443 localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// https
|
||||
add(step{reset: true}) |
||||
add(step{ // allow omitting port (default to 80)
|
||||
command: cmd("serve --http=80 --bg http://localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // support non Funnel port
|
||||
command: cmd("serve --http=9999 --set-path=/abc http://localhost:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --http=9999 --set-path=/abc off"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --http=8080 --set-path=/abc http://127.0.0.1:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8080": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// // https
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg http://localhost:0"), // invalid port, too low
|
||||
wantErr: anyErr(), |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg http://localhost:65536"), // invalid port, too high
|
||||
wantErr: anyErr(), |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg http://somehost:3000"), // invalid host
|
||||
wantErr: anyErr(), |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg httpz://127.0.0.1"), // invalid scheme
|
||||
wantErr: anyErr(), |
||||
}) |
||||
add(step{ // allow omitting port (default to 443)
|
||||
command: cmd("serve --https=443 --bg http://localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // support non Funnel port
|
||||
command: cmd("serve --https=9999 --set-path=/abc http://localhost:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 9999: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=9999 --set-path=/abc off"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=8443 --set-path=/abc http://127.0.0.1:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=10000 --bg text:hi"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: {HTTPS: true}, 8443: {HTTPS: true}, 10000: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
"foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Text: "hi"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --set-path=/foo off"), |
||||
want: nil, // nothing to save
|
||||
wantErr: anyErr(), |
||||
}) // handler doesn't exist, so we get an error
|
||||
add(step{ |
||||
command: cmd("serve --https=10000 off"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 off"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/abc": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=8443 --set-path=/abc off"), |
||||
want: &ipn.ServeConfig{}, |
||||
}) |
||||
add(step{ // clean mount: "bar" becomes "/bar"
|
||||
command: cmd("serve --https=443 --set-path=bar https://127.0.0.1:8443"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/bar": {Proxy: "https://127.0.0.1:8443"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
// add(step{
|
||||
// command: cmd("serve --https=443 --set-path=bar https://127.0.0.1:8443"),
|
||||
// want: nil, // nothing to save
|
||||
// })
|
||||
add(step{ // try resetting using reset command
|
||||
command: cmd("serve reset"), |
||||
want: &ipn.ServeConfig{}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg https+insecure://127.0.0.1:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "https+insecure://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --set-path=/foo localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/foo": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // test a second handler on the same port
|
||||
command: cmd("serve --https=8443 --set-path=/foo localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/foo": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/foo": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{reset: true}) |
||||
add(step{ // support path in proxy
|
||||
command: cmd("serve --https=443 --bg http://127.0.0.1:3000/foo/bar"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000/foo/bar"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// // tcp
|
||||
add(step{reset: true}) |
||||
add(step{ // must include scheme for tcp
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg localhost:5432"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{ // !somehost, must be localhost or 127.0.0.1
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:5432"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{ // bad target port, too low
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:0"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{ // bad target port, too high
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:65536"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:5432", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:8443", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
// add(step{
|
||||
// command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"),
|
||||
// want: nil, // nothing to save
|
||||
// })
|
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:8444"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:8444", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8445"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:8445", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:123"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:123", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // handler doesn't exist, so we get an error
|
||||
command: cmd("serve --tls-terminated-tcp=8443 off"), |
||||
wantErr: anyErr(), |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --tls-terminated-tcp=443 off"), |
||||
want: &ipn.ServeConfig{}, |
||||
}) |
||||
|
||||
// // text
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg text:hello"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Text: "hello"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// path
|
||||
td := t.TempDir() |
||||
writeFile := func(suffix, contents string) { |
||||
if err := os.WriteFile(filepath.Join(td, suffix), []byte(contents), 0600); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
add(step{reset: true}) |
||||
writeFile("foo", "this is foo") |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg " + filepath.Join(td, "foo")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Path: filepath.Join(td, "foo")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
os.MkdirAll(filepath.Join(td, "subdir"), 0700) |
||||
writeFile("subdir/file-a", "this is A") |
||||
add(step{ |
||||
command: cmd("serve --https=443 --set-path=/some/where " + filepath.Join(td, "subdir/file-a")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Path: filepath.Join(td, "foo")}, |
||||
"/some/where": {Path: filepath.Join(td, "subdir/file-a")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // bad path
|
||||
command: cmd("serve --https=443 --bg bad/path"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 --bg " + filepath.Join(td, "subdir")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Path: filepath.Join(td, "subdir/")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ |
||||
command: cmd("serve --https=443 off"), |
||||
want: &ipn.ServeConfig{}, |
||||
}) |
||||
|
||||
// // combos
|
||||
add(step{reset: true}) |
||||
add(step{ |
||||
command: cmd("serve --bg localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // enable funnel for primary port
|
||||
command: cmd("funnel --bg localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // serving on secondary port doesn't change funnel on primary port
|
||||
command: cmd("serve --https=8443 --set-path=/bar localhost:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/bar": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // turn funnel on for secondary port
|
||||
command: cmd("funnel --https=8443 --set-path=/bar localhost:3001"), |
||||
want: &ipn.ServeConfig{ |
||||
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true, "foo.test.ts.net:8443": true}, |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/bar": {Proxy: "http://127.0.0.1:3001"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
// TODO(tylersmalley) resolve these failures
|
||||
// add(step{ // turn funnel off for primary port 443
|
||||
// command: cmd("serve --https=443 --set-path=/bar localhost:3001"),
|
||||
// want: &ipn.ServeConfig{
|
||||
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
|
||||
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
|
||||
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
// "/": {Proxy: "http://127.0.0.1:3000"},
|
||||
// }},
|
||||
// "foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
// "/bar": {Proxy: "http://127.0.0.1:3001"},
|
||||
// }},
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// add(step{ // remove secondary port
|
||||
// command: cmd("https:8443 /bar off"),
|
||||
// want: &ipn.ServeConfig{
|
||||
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
|
||||
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
||||
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
// "/": {Proxy: "http://127.0.0.1:3000"},
|
||||
// }},
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// add(step{ // start a tcp forwarder on 8443
|
||||
// command: cmd("tcp:8443 tcp://localhost:5432"),
|
||||
// want: &ipn.ServeConfig{
|
||||
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
|
||||
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {TCPForward: "127.0.0.1:5432"}},
|
||||
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
// "/": {Proxy: "http://127.0.0.1:3000"},
|
||||
// }},
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// add(step{ // remove primary port http handler
|
||||
// command: cmd("https:443 / off"),
|
||||
// want: &ipn.ServeConfig{
|
||||
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
|
||||
// TCP: map[uint16]*ipn.TCPPortHandler{8443: {TCPForward: "127.0.0.1:5432"}},
|
||||
// },
|
||||
// })
|
||||
// add(step{ // remove tcp forwarder
|
||||
// command: cmd("tls-terminated-tcp:8443 off"),
|
||||
// want: &ipn.ServeConfig{
|
||||
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
|
||||
// },
|
||||
// })
|
||||
// add(step{ // turn off funnel
|
||||
// command: cmd("funnel 8443 off"),
|
||||
// want: &ipn.ServeConfig{},
|
||||
// })
|
||||
|
||||
// // tricky steps
|
||||
add(step{reset: true}) |
||||
add(step{ // a directory with a trailing slash mount point
|
||||
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "subdir")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/dir/": {Path: filepath.Join(td, "subdir/")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // this should overwrite the previous one
|
||||
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "foo")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/dir": {Path: filepath.Join(td, "foo")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{reset: true}) // reset and do the opposite
|
||||
add(step{ // a file without a trailing slash mount point
|
||||
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "foo")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/dir": {Path: filepath.Join(td, "foo")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // this should overwrite the previous one
|
||||
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "subdir")), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/dir/": {Path: filepath.Join(td, "subdir/")}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
// // error states
|
||||
add(step{reset: true}) |
||||
add(step{ // tcp forward 5432 on serve port 443
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{ |
||||
443: { |
||||
TCPForward: "127.0.0.1:5432", |
||||
TerminateTLS: "foo.test.ts.net", |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // try to start a web handler on the same port
|
||||
command: cmd("serve --https=443 --bg localhost:3000"), |
||||
wantErr: exactErr(errHelp, "errHelp"), |
||||
}) |
||||
add(step{reset: true}) |
||||
add(step{ // start a web handler on port 443
|
||||
command: cmd("serve --https=443 --bg localhost:3000"), |
||||
want: &ipn.ServeConfig{ |
||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, |
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{ |
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ |
||||
"/": {Proxy: "http://127.0.0.1:3000"}, |
||||
}}, |
||||
}, |
||||
}, |
||||
}) |
||||
add(step{ // try to start a tcp forwarder on the same serve port
|
||||
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), |
||||
wantErr: anyErr(), |
||||
}) |
||||
|
||||
lc := &fakeLocalServeClient{} |
||||
// And now run the steps above.
|
||||
for i, st := range steps { |
||||
if st.debugBreak != nil { |
||||
st.debugBreak() |
||||
} |
||||
if st.reset { |
||||
t.Logf("Executing step #%d, line %v: [reset]", i, st.line) |
||||
lc.config = nil |
||||
} |
||||
if st.command == nil { |
||||
continue |
||||
} |
||||
t.Logf("Executing step #%d, line %v: %q ... ", i, st.line, st.command) |
||||
|
||||
var stdout bytes.Buffer |
||||
var flagOut bytes.Buffer |
||||
e := &serveEnv{ |
||||
lc: lc, |
||||
testFlagOut: &flagOut, |
||||
testStdout: &stdout, |
||||
} |
||||
lastCount := lc.setCount |
||||
var cmd *ffcli.Command |
||||
var args []string |
||||
|
||||
mode := serve |
||||
if st.command[0] == "funnel" { |
||||
mode = funnel |
||||
} |
||||
cmd = newServeDevCommand(e, mode) |
||||
args = st.command[1:] |
||||
|
||||
err := cmd.ParseAndRun(context.Background(), args) |
||||
if flagOut.Len() > 0 { |
||||
t.Logf("flag package output: %q", flagOut.Bytes()) |
||||
} |
||||
if err != nil { |
||||
if st.wantErr == nil { |
||||
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, err) |
||||
} |
||||
if bad := st.wantErr(err); bad != "" { |
||||
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, bad) |
||||
} |
||||
continue |
||||
} |
||||
if st.wantErr != nil { |
||||
t.Fatalf("step #%d, line %v: got success (saved=%v), but wanted an error", i, st.line, lc.config != nil) |
||||
} |
||||
var got *ipn.ServeConfig = nil |
||||
if lc.setCount > lastCount { |
||||
got = lc.config |
||||
} |
||||
if !reflect.DeepEqual(got, st.want) { |
||||
t.Fatalf("[%d] %v: bad state. got:\n%v\n\nwant:\n%v\n", |
||||
i, st.command, logger.AsJSON(got), logger.AsJSON(st.want)) |
||||
// NOTE: asJSON will omit empty fields, which might make
|
||||
// result in bad state got/want diffs being the same, even
|
||||
// though the actual state is different. Use below to debug:
|
||||
// t.Fatalf("[%d] %v: bad state. got:\n%+v\n\nwant:\n%+v\n",
|
||||
// i, st.command, got, st.want)
|
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestSrcTypeFromFlags(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
env *serveEnv |
||||
expectedType string |
||||
expectedPort uint16 |
||||
expectedErr bool |
||||
}{ |
||||
{ |
||||
name: "only http set", |
||||
env: &serveEnv{http: "80"}, |
||||
expectedType: "http", |
||||
expectedPort: 80, |
||||
expectedErr: false, |
||||
}, |
||||
{ |
||||
name: "only https set", |
||||
env: &serveEnv{https: "10000"}, |
||||
expectedType: "https", |
||||
expectedPort: 10000, |
||||
expectedErr: false, |
||||
}, |
||||
{ |
||||
name: "only tcp set", |
||||
env: &serveEnv{tcp: "8000"}, |
||||
expectedType: "tcp", |
||||
expectedPort: 8000, |
||||
expectedErr: false, |
||||
}, |
||||
{ |
||||
name: "only tls-terminated-tcp set", |
||||
env: &serveEnv{tlsTerminatedTcp: "8080"}, |
||||
expectedType: "tls-terminated-tcp", |
||||
expectedPort: 8080, |
||||
expectedErr: false, |
||||
}, |
||||
{ |
||||
name: "defaults to https, port 443", |
||||
env: &serveEnv{}, |
||||
expectedType: "https", |
||||
expectedPort: 443, |
||||
expectedErr: false, |
||||
}, |
||||
{ |
||||
name: "multiple types set", |
||||
env: &serveEnv{http: "80", https: "443"}, |
||||
expectedType: "", |
||||
expectedPort: 0, |
||||
expectedErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
srcType, srcPort, err := srvTypeAndPortFromFlags(tt.env) |
||||
if (err != nil) != tt.expectedErr { |
||||
t.Errorf("Expected error: %v, got: %v", tt.expectedErr, err) |
||||
} |
||||
if srcType != tt.expectedType { |
||||
t.Errorf("Expected srcType: %s, got: %s", tt.expectedType, srcType) |
||||
} |
||||
if srcPort != tt.expectedPort { |
||||
t.Errorf("Expected srcPort: %d, got: %d", tt.expectedPort, srcPort) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue