TUN-3295: Show route command results
This commit is contained in:
parent
20623255dd
commit
85d0afd3b0
|
@ -244,10 +244,10 @@ func adhocNamedTunnel(c *cli.Context, name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if r, ok := routeFromFlag(c); ok {
|
if r, ok := routeFromFlag(c); ok {
|
||||||
if err := sc.route(tunnel.ID, r); err != nil {
|
if res, err := sc.route(tunnel.ID, r); err != nil {
|
||||||
sc.logger.Errorf("failed to create route, please create it manually. err: %v.", err)
|
sc.logger.Errorf("failed to create route, please create it manually. err: %v.", err)
|
||||||
} else {
|
} else {
|
||||||
sc.logger.Infof(r.SuccessSummary())
|
sc.logger.Infof(res.SuccessSummary())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -270,17 +270,13 @@ func (sc *subcommandContext) cleanupConnections(tunnelIDs []uuid.UUID) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *subcommandContext) route(tunnelID uuid.UUID, r tunnelstore.Route) error {
|
func (sc *subcommandContext) route(tunnelID uuid.UUID, r tunnelstore.Route) (tunnelstore.RouteResult, error) {
|
||||||
client, err := sc.client()
|
client, err := sc.client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.RouteTunnel(tunnelID, r); err != nil {
|
return client.RouteTunnel(tunnelID, r)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *subcommandContext) tunnelActive(name string) (*tunnelstore.Tunnel, bool, error) {
|
func (sc *subcommandContext) tunnelActive(name string) (*tunnelstore.Tunnel, bool, error) {
|
||||||
|
|
|
@ -438,7 +438,7 @@ func routeCommand(c *cli.Context) error {
|
||||||
const tunnelIDIndex = 1
|
const tunnelIDIndex = 1
|
||||||
|
|
||||||
routeType := c.Args().First()
|
routeType := c.Args().First()
|
||||||
var r tunnelstore.Route
|
var route tunnelstore.Route
|
||||||
var tunnelID uuid.UUID
|
var tunnelID uuid.UUID
|
||||||
switch routeType {
|
switch routeType {
|
||||||
case "dns":
|
case "dns":
|
||||||
|
@ -446,7 +446,7 @@ func routeCommand(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err = dnsRouteFromArg(c)
|
route, err = dnsRouteFromArg(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -455,7 +455,7 @@ func routeCommand(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err = lbRouteFromArg(c)
|
route, err = lbRouteFromArg(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -463,10 +463,12 @@ func routeCommand(c *cli.Context) error {
|
||||||
return cliutil.UsageError("%s is not a recognized route type. Supported route types are dns and lb", routeType)
|
return cliutil.UsageError("%s is not a recognized route type. Supported route types are dns and lb", routeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sc.route(tunnelID, r); err != nil {
|
res, err := sc.route(tunnelID, route)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sc.logger.Infof(r.SuccessSummary())
|
|
||||||
|
sc.logger.Infof(res.SuccessSummary())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,10 +43,22 @@ type Connection struct {
|
||||||
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Change = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChangeNew = "new"
|
||||||
|
ChangeUpdated = "updated"
|
||||||
|
ChangeUnchanged = "unchanged"
|
||||||
|
)
|
||||||
|
|
||||||
// Route represents a record type that can route to a tunnel
|
// Route represents a record type that can route to a tunnel
|
||||||
type Route interface {
|
type Route interface {
|
||||||
json.Marshaler
|
json.Marshaler
|
||||||
RecordType() string
|
RecordType() string
|
||||||
|
UnmarshalResult(body io.Reader) (RouteResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteResult interface {
|
||||||
// SuccessSummary explains what will route to this tunnel when it's provisioned successfully
|
// SuccessSummary explains what will route to this tunnel when it's provisioned successfully
|
||||||
SuccessSummary() string
|
SuccessSummary() string
|
||||||
}
|
}
|
||||||
|
@ -55,6 +67,11 @@ type DNSRoute struct {
|
||||||
userHostname string
|
userHostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DNSRouteResult struct {
|
||||||
|
route *DNSRoute
|
||||||
|
CName Change `json:"cname"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewDNSRoute(userHostname string) Route {
|
func NewDNSRoute(userHostname string) Route {
|
||||||
return &DNSRoute{
|
return &DNSRoute{
|
||||||
userHostname: userHostname,
|
userHostname: userHostname,
|
||||||
|
@ -72,12 +89,30 @@ func (dr *DNSRoute) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(&s)
|
return json.Marshal(&s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dr *DNSRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
||||||
|
var result DNSRouteResult
|
||||||
|
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.route = dr
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (dr *DNSRoute) RecordType() string {
|
func (dr *DNSRoute) RecordType() string {
|
||||||
return "dns"
|
return "dns"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *DNSRoute) SuccessSummary() string {
|
func (res *DNSRouteResult) SuccessSummary() string {
|
||||||
return fmt.Sprintf("%s will route to your tunnel", dr.userHostname)
|
var msgFmt string
|
||||||
|
switch res.CName {
|
||||||
|
case ChangeNew:
|
||||||
|
msgFmt = "Added CNAME %s which will route to this tunnel"
|
||||||
|
case ChangeUpdated: // this is not currently returned by tunnelsore
|
||||||
|
msgFmt = "%s updated to route to your tunnel"
|
||||||
|
case ChangeUnchanged:
|
||||||
|
msgFmt = "%s is already configured to route to your tunnel"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msgFmt, res.route.userHostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LBRoute struct {
|
type LBRoute struct {
|
||||||
|
@ -85,6 +120,12 @@ type LBRoute struct {
|
||||||
lbPool string
|
lbPool string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LBRouteResult struct {
|
||||||
|
route *LBRoute
|
||||||
|
LoadBalancer Change `json:"load_balancer"`
|
||||||
|
Pool Change `json:"pool"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewLBRoute(lbName, lbPool string) Route {
|
func NewLBRoute(lbName, lbPool string) Route {
|
||||||
return &LBRoute{
|
return &LBRoute{
|
||||||
lbName: lbName,
|
lbName: lbName,
|
||||||
|
@ -109,8 +150,42 @@ func (lr *LBRoute) RecordType() string {
|
||||||
return "lb"
|
return "lb"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *LBRoute) SuccessSummary() string {
|
func (lr *LBRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
||||||
return fmt.Sprintf("Load balancer %s will route to this tunnel through pool %s", lr.lbName, lr.lbPool)
|
var result LBRouteResult
|
||||||
|
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.route = lr
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *LBRouteResult) SuccessSummary() string {
|
||||||
|
var msg string
|
||||||
|
switch res.LoadBalancer + "," + res.Pool {
|
||||||
|
case "new,new":
|
||||||
|
msg = "Created load balancer %s and added a new pool %s with this tunnel as an origin"
|
||||||
|
case "new,updated":
|
||||||
|
msg = "Created load balancer %s with an existing pool %s which was updated to use this tunnel as an origin"
|
||||||
|
case "new,unchanged":
|
||||||
|
msg = "Created load balancer %s with an existing pool %s which already has this tunnel as an origin"
|
||||||
|
case "updated,new":
|
||||||
|
msg = "Added new pool %[2]s with this tunnel as an origin to load balancer %[1]s"
|
||||||
|
case "updated,updated":
|
||||||
|
msg = "Updated pool %[2]s to use this tunnel as an origin and added it to load balancer %[1]s"
|
||||||
|
case "updated,unchanged":
|
||||||
|
msg = "Added pool %[2]s, which already has this tunnel as an origin, to load balancer %[1]s"
|
||||||
|
case "unchanged,updated":
|
||||||
|
msg = "Added this tunnel as an origin in pool %[2]s which is already used by load balancer %[1]s"
|
||||||
|
case "unchanged,unchanged":
|
||||||
|
msg = "Load balancer %s already uses pool %s which has this tunnel as an origin"
|
||||||
|
case "unchanged,new":
|
||||||
|
// this state is not possible
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
msg = "Something went wrong: failed to modify load balancer %s with pool %s; please check traffic manager configuration in the dashboard"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(msg, res.route.lbName, res.route.lbPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
@ -119,7 +194,7 @@ type Client interface {
|
||||||
DeleteTunnel(tunnelID uuid.UUID) error
|
DeleteTunnel(tunnelID uuid.UUID) error
|
||||||
ListTunnels(filter *Filter) ([]*Tunnel, error)
|
ListTunnels(filter *Filter) ([]*Tunnel, error)
|
||||||
CleanupConnections(tunnelID uuid.UUID) error
|
CleanupConnections(tunnelID uuid.UUID) error
|
||||||
RouteTunnel(tunnelID uuid.UUID, route Route) error
|
RouteTunnel(tunnelID uuid.UUID, route Route) (RouteResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RESTClient struct {
|
type RESTClient struct {
|
||||||
|
@ -260,16 +335,20 @@ func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID) error {
|
||||||
return r.statusCodeToError("cleanup connections", resp)
|
return r.statusCodeToError("cleanup connections", resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RESTClient) RouteTunnel(tunnelID uuid.UUID, route Route) error {
|
func (r *RESTClient) RouteTunnel(tunnelID uuid.UUID, route Route) (RouteResult, error) {
|
||||||
endpoint := r.baseEndpoints.zoneLevel
|
endpoint := r.baseEndpoints.zoneLevel
|
||||||
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/routes", tunnelID))
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/routes", tunnelID))
|
||||||
resp, err := r.sendRequest("PUT", endpoint, route)
|
resp, err := r.sendRequest("PUT", endpoint, route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "REST request failed")
|
return nil, errors.Wrap(err, "REST request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
return r.statusCodeToError("add route", resp)
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
return route.UnmarshalResult(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, r.statusCodeToError("add route", resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) (*http.Response, error) {
|
func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) (*http.Response, error) {
|
||||||
|
@ -304,10 +383,10 @@ func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
|
||||||
|
|
||||||
func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
|
func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
|
||||||
if resp.Header.Get("Content-Type") == "application/json" {
|
if resp.Header.Get("Content-Type") == "application/json" {
|
||||||
var errorsResp struct{
|
var errorsResp struct {
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
if json.NewDecoder(resp.Body).Decode(&errorsResp) == nil && errorsResp.Error != ""{
|
if json.NewDecoder(resp.Body).Decode(&errorsResp) == nil && errorsResp.Error != "" {
|
||||||
return errors.Errorf("Failed to %s: %s", op, errorsResp.Error)
|
return errors.Errorf("Failed to %s: %s", op, errorsResp.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package tunnelstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSRouteUnmarshalResult(t *testing.T) {
|
||||||
|
route := &DNSRoute{
|
||||||
|
userHostname: "example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := route.UnmarshalResult(strings.NewReader(`{"cname": "new"}`))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &DNSRouteResult{
|
||||||
|
route: route,
|
||||||
|
CName: ChangeNew,
|
||||||
|
}, result)
|
||||||
|
|
||||||
|
_, err = route.UnmarshalResult(strings.NewReader(`abc`))
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLBRouteUnmarshalResult(t *testing.T) {
|
||||||
|
route := &LBRoute{
|
||||||
|
lbName: "lb.example.com",
|
||||||
|
lbPool: "pool",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := route.UnmarshalResult(strings.NewReader(`{"pool": "unchanged", "load_balancer": "updated"}`))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &LBRouteResult{
|
||||||
|
route: route,
|
||||||
|
LoadBalancer: ChangeUpdated,
|
||||||
|
Pool: ChangeUnchanged,
|
||||||
|
}, result)
|
||||||
|
|
||||||
|
_, err = route.UnmarshalResult(strings.NewReader(`abc`))
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLBRouteResultSuccessSummary(t *testing.T) {
|
||||||
|
route := &LBRoute{
|
||||||
|
lbName: "lb.example.com",
|
||||||
|
lbPool: "POOL",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
lb Change
|
||||||
|
pool Change
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ChangeNew, ChangeNew, "Created load balancer lb.example.com and added a new pool POOL with this tunnel as an origin" },
|
||||||
|
{ChangeNew, ChangeUpdated, "Created load balancer lb.example.com with an existing pool POOL which was updated to use this tunnel as an origin" },
|
||||||
|
{ChangeNew, ChangeUnchanged, "Created load balancer lb.example.com with an existing pool POOL which already has this tunnel as an origin" },
|
||||||
|
{ChangeUpdated, ChangeNew, "Added new pool POOL with this tunnel as an origin to load balancer lb.example.com" },
|
||||||
|
{ChangeUpdated, ChangeUpdated, "Updated pool POOL to use this tunnel as an origin and added it to load balancer lb.example.com" },
|
||||||
|
{ChangeUpdated, ChangeUnchanged, "Added pool POOL, which already has this tunnel as an origin, to load balancer lb.example.com" },
|
||||||
|
{ChangeUnchanged, ChangeNew, "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard" },
|
||||||
|
{ChangeUnchanged, ChangeUpdated, "Added this tunnel as an origin in pool POOL which is already used by load balancer lb.example.com" },
|
||||||
|
{ChangeUnchanged, ChangeUnchanged, "Load balancer lb.example.com already uses pool POOL which has this tunnel as an origin" },
|
||||||
|
{"", "", "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard" },
|
||||||
|
{"a", "b", "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard" },
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
res := &LBRouteResult{
|
||||||
|
route: route,
|
||||||
|
LoadBalancer: tt.lb,
|
||||||
|
Pool: tt.pool,
|
||||||
|
}
|
||||||
|
actual := res.SuccessSummary()
|
||||||
|
assert.Equal(t, tt.expected, actual, "case %d", i+1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue