diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index e238b62e..e5dacb01 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -77,6 +77,12 @@ var listRoutesCmd = &cobra.Command{ return } + if output != "" { + SuccessOutput(response.Routes, "", output) + + return + } + routes = response.Routes } else { response, err := client.GetMachineRoutes(ctx, &v1.GetMachineRoutesRequest{ @@ -92,6 +98,12 @@ var listRoutesCmd = &cobra.Command{ return } + if output != "" { + SuccessOutput(response.Routes, "", output) + + return + } + routes = response.Routes } diff --git a/grpcv1.go b/grpcv1.go index 0c14a5ee..7b12fa05 100644 --- a/grpcv1.go +++ b/grpcv1.go @@ -521,6 +521,7 @@ func (api headscaleV1APIServer) DebugCreateMachine( HostInfo: HostInfo(hostinfo), } + nodeKey := key.NodePublic{} err = nodeKey.UnmarshalText([]byte(request.GetKey())) if err != nil { diff --git a/integration/cli_test.go b/integration/cli_test.go index 58ef826a..0ed350cf 100644 --- a/integration/cli_test.go +++ b/integration/cli_test.go @@ -2,7 +2,9 @@ package integration import ( "encoding/json" + "fmt" "sort" + "strconv" "testing" "time" @@ -388,3 +390,144 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { err = scenario.Shutdown() assert.NoError(t, err) } + +func TestEnablingRoutes(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + namespace := "enable-routing" + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + namespace: 3, + } + + err = scenario.CreateHeadscaleEnv(spec, hsic.WithTestName("clienableroute")) + assert.NoError(t, err) + + allClients, err := scenario.ListTailscaleClients() + if err != nil { + t.Errorf("failed to get clients: %s", err) + } + + err = scenario.WaitForTailscaleSync() + if err != nil { + t.Errorf("failed wait for tailscale clients to be in sync: %s", err) + } + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + // advertise routes using the up command + for i, client := range allClients { + routeStr := fmt.Sprintf("10.0.%d.0/24", i) + hostname, _ := client.FQDN() + _, _, err = client.Execute([]string{ + "tailscale", + "up", + fmt.Sprintf("--advertise-routes=%s", routeStr), + "-login-server", headscale.GetEndpoint(), + "--hostname", hostname, + }) + assert.NoError(t, err) + } + + err = scenario.WaitForTailscaleSync() + if err != nil { + t.Errorf("failed wait for tailscale clients to be in sync: %s", err) + } + + var routes []*v1.Route + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "routes", + "list", + "--output", + "json", + }, + &routes, + ) + + assert.NoError(t, err) + assert.Len(t, routes, 3) + + for _, route := range routes { + assert.Equal(t, route.Advertised, true) + assert.Equal(t, route.Enabled, false) + assert.Equal(t, route.IsPrimary, false) + } + + for _, route := range routes { + _, err = headscale.Execute( + []string{ + "headscale", + "routes", + "enable", + "--route", + strconv.Itoa(int(route.Id)), + }) + assert.NoError(t, err) + } + + var enablingRoutes []*v1.Route + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "routes", + "list", + "--output", + "json", + }, + &enablingRoutes, + ) + assert.NoError(t, err) + + for _, route := range enablingRoutes { + assert.Equal(t, route.Advertised, true) + assert.Equal(t, route.Enabled, true) + assert.Equal(t, route.IsPrimary, true) + } + + routeIDToBeDisabled := enablingRoutes[0].Id + + _, err = headscale.Execute( + []string{ + "headscale", + "routes", + "disable", + "--route", + strconv.Itoa(int(routeIDToBeDisabled)), + }) + assert.NoError(t, err) + + var disablingRoutes []*v1.Route + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "routes", + "list", + "--output", + "json", + }, + &disablingRoutes, + ) + assert.NoError(t, err) + + for _, route := range disablingRoutes { + assert.Equal(t, true, route.Advertised) + + if route.Id == routeIDToBeDisabled { + assert.Equal(t, route.Enabled, false) + assert.Equal(t, route.IsPrimary, false) + } else { + assert.Equal(t, route.Enabled, true) + assert.Equal(t, route.IsPrimary, true) + } + } +} diff --git a/integration_cli_test.go b/integration_cli_test.go index 62df6b31..537bf08b 100644 --- a/integration_cli_test.go +++ b/integration_cli_test.go @@ -7,7 +7,6 @@ import ( "log" "net/http" "os" - "strconv" "testing" "time" @@ -1244,173 +1243,6 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() { assert.Contains(s.T(), listAllAfterRenameAttempt[4].GetGivenName(), "machine-5") } -func (s *IntegrationCLITestSuite) TestRouteCommand() { - namespace, err := s.createNamespace("routes-namespace") - assert.Nil(s.T(), err) - - // Randomly generated machine keys - machineKey := "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe" - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - "route-machine", - "--namespace", - namespace.Name, - "--key", - machineKey, - "--route", - "10.0.0.0/8", - "--route", - "192.168.1.0/24", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--namespace", - namespace.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), uint64(1), machine.Id) - assert.Equal(s.T(), "route-machine", machine.Name) - - listAllResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "routes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var routes []v1.Route - err = json.Unmarshal([]byte(listAllResult), &routes) - assert.Nil(s.T(), err) - - assert.Len(s.T(), routes, 2) - assert.Equal(s.T(), routes[0].Enabled, false) - assert.Equal(s.T(), routes[1].Enabled, false) - - routeIDToEnable := routes[1].Id - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "routes", - "enable", - "--output", - "json", - "--route", - strconv.FormatUint(routeIDToEnable, 10), - }, - []string{}, - ) - assert.Nil(s.T(), err) - - listAllResult, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "routes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - assert.Nil(s.T(), err) - - err = json.Unmarshal([]byte(listAllResult), &routes) - assert.Nil(s.T(), err) - - assert.Len(s.T(), routes, 2) - - for _, route := range routes { - if route.Id == routeIDToEnable { - assert.Equal(s.T(), route.Enabled, true) - assert.Equal(s.T(), route.IsPrimary, true) - } else { - assert.Equal(s.T(), route.Enabled, false) - } - } - - // Enable only one route, effectively disabling one of the routes - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "routes", - "disable", - "--output", - "json", - "--route", - strconv.FormatUint(routeIDToEnable, 10), - }, - []string{}, - ) - assert.Nil(s.T(), err) - - listAllResult, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "routes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - assert.Nil(s.T(), err) - - err = json.Unmarshal([]byte(listAllResult), &routes) - assert.Nil(s.T(), err) - - assert.Len(s.T(), routes, 2) - - for _, route := range routes { - if route.Id == routeIDToEnable { - assert.Equal(s.T(), route.Enabled, false) - assert.Equal(s.T(), route.IsPrimary, false) - } else { - assert.Equal(s.T(), route.Enabled, false) - } - } -} - func (s *IntegrationCLITestSuite) TestApiKeyCommand() { count := 5