mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 08:53:05 +00:00
* #2140 Fixed updating of hostname and givenName when it is updated in HostInfo * #2140 Added integration tests * #2140 Fix unit tests * Changed IsAutomaticNameMode to GivenNameHasBeenChanged. Fixed errors in files according to golangci-lint rules
This commit is contained in:
parent
45c9585b52
commit
b6dc6eb36c
6 changed files with 221 additions and 2 deletions
1
.github/workflows/test-integration.yaml
vendored
1
.github/workflows/test-integration.yaml
vendored
|
@ -50,6 +50,7 @@ jobs:
|
||||||
- TestEphemeral2006DeletedTooQuickly
|
- TestEphemeral2006DeletedTooQuickly
|
||||||
- TestPingAllByHostname
|
- TestPingAllByHostname
|
||||||
- TestTaildrop
|
- TestTaildrop
|
||||||
|
- TestUpdateHostnameFromClient
|
||||||
- TestExpireNode
|
- TestExpireNode
|
||||||
- TestNodeOnlineStatus
|
- TestNodeOnlineStatus
|
||||||
- TestPingAllByIPManyUpDown
|
- TestPingAllByIPManyUpDown
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
- Allow nodes to use SSH agent forwarding [#2145](https://github.com/juanfont/headscale/pull/2145)
|
- Allow nodes to use SSH agent forwarding [#2145](https://github.com/juanfont/headscale/pull/2145)
|
||||||
- Fixed processing of fields in post request in MoveNode rpc [#2179](https://github.com/juanfont/headscale/pull/2179)
|
- Fixed processing of fields in post request in MoveNode rpc [#2179](https://github.com/juanfont/headscale/pull/2179)
|
||||||
- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
|
- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
|
||||||
|
- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
|
||||||
|
|
||||||
## 0.23.0 (2024-09-18)
|
## 0.23.0 (2024-09-18)
|
||||||
|
|
||||||
|
|
|
@ -471,7 +471,7 @@ func (m *mapSession) handleEndpointUpdate() {
|
||||||
|
|
||||||
// Check if the Hostinfo of the node has changed.
|
// Check if the Hostinfo of the node has changed.
|
||||||
// If it has changed, check if there has been a change to
|
// If it has changed, check if there has been a change to
|
||||||
// the routable IPs of the host and update update them in
|
// the routable IPs of the host and update them in
|
||||||
// the database. Then send a Changed update
|
// the database. Then send a Changed update
|
||||||
// (containing the whole node object) to peers to inform about
|
// (containing the whole node object) to peers to inform about
|
||||||
// the route change.
|
// the route change.
|
||||||
|
@ -510,6 +510,12 @@ func (m *mapSession) handleEndpointUpdate() {
|
||||||
m.node.ID)
|
m.node.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there has been a change to Hostname and update them
|
||||||
|
// in the database. Then send a Changed update
|
||||||
|
// (containing the whole node object) to peers to inform about
|
||||||
|
// the hostname change.
|
||||||
|
m.node.ApplyHostnameFromHostInfo(m.req.Hostinfo)
|
||||||
|
|
||||||
if err := m.h.db.DB.Save(m.node).Error; err != nil {
|
if err := m.h.db.DB.Save(m.node).Error; err != nil {
|
||||||
m.errf(err, "Failed to persist/update node in the database")
|
m.errf(err, "Failed to persist/update node in the database")
|
||||||
http.Error(m.w, "", http.StatusInternalServerError)
|
http.Error(m.w, "", http.StatusInternalServerError)
|
||||||
|
@ -526,7 +532,8 @@ func (m *mapSession) handleEndpointUpdate() {
|
||||||
ChangeNodes: []types.NodeID{m.node.ID},
|
ChangeNodes: []types.NodeID{m.node.ID},
|
||||||
Message: "called from handlePoll -> update",
|
Message: "called from handlePoll -> update",
|
||||||
},
|
},
|
||||||
m.node.ID)
|
m.node.ID,
|
||||||
|
)
|
||||||
|
|
||||||
m.w.WriteHeader(http.StatusOK)
|
m.w.WriteHeader(http.StatusOK)
|
||||||
mapResponseEndpointUpdates.WithLabelValues("ok").Inc()
|
mapResponseEndpointUpdates.WithLabelValues("ok").Inc()
|
||||||
|
|
|
@ -97,6 +97,11 @@ type (
|
||||||
Nodes []*Node
|
Nodes []*Node
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GivenNameHasBeenChanged returns whether the `givenName` can be automatically changed based on the `Hostname` of the node.
|
||||||
|
func (node *Node) GivenNameHasBeenChanged() bool {
|
||||||
|
return node.GivenName == util.ConvertWithFQDNRules(node.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
// IsExpired returns whether the node registration has expired.
|
// IsExpired returns whether the node registration has expired.
|
||||||
func (node Node) IsExpired() bool {
|
func (node Node) IsExpired() bool {
|
||||||
// If Expiry is not set, the client has not indicated that
|
// If Expiry is not set, the client has not indicated that
|
||||||
|
@ -347,6 +352,21 @@ func (node *Node) RegisterMethodToV1Enum() v1.RegisterMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyHostnameFromHostInfo takes a Hostinfo struct and updates the node.
|
||||||
|
func (node *Node) ApplyHostnameFromHostInfo(hostInfo *tailcfg.Hostinfo) {
|
||||||
|
if hostInfo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Hostname != hostInfo.Hostname {
|
||||||
|
if node.GivenNameHasBeenChanged() {
|
||||||
|
node.GivenName = util.ConvertWithFQDNRules(hostInfo.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Hostname = hostInfo.Hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyPeerChange takes a PeerChange struct and updates the node.
|
// ApplyPeerChange takes a PeerChange struct and updates the node.
|
||||||
func (node *Node) ApplyPeerChange(change *tailcfg.PeerChange) {
|
func (node *Node) ApplyPeerChange(change *tailcfg.PeerChange) {
|
||||||
if change.Key != nil {
|
if change.Key != nil {
|
||||||
|
|
|
@ -337,6 +337,66 @@ func TestPeerChangeFromMapRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyHostnameFromHostInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
nodeBefore Node
|
||||||
|
change *tailcfg.Hostinfo
|
||||||
|
want Node
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "hostinfo-not-exists",
|
||||||
|
nodeBefore: Node{
|
||||||
|
GivenName: "manual-test.local",
|
||||||
|
Hostname: "TestHost.Local",
|
||||||
|
},
|
||||||
|
change: nil,
|
||||||
|
want: Node{
|
||||||
|
GivenName: "manual-test.local",
|
||||||
|
Hostname: "TestHost.Local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostinfo-exists-no-automatic-givenName",
|
||||||
|
nodeBefore: Node{
|
||||||
|
GivenName: "manual-test.local",
|
||||||
|
Hostname: "TestHost.Local",
|
||||||
|
},
|
||||||
|
change: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "NewHostName.Local",
|
||||||
|
},
|
||||||
|
want: Node{
|
||||||
|
GivenName: "manual-test.local",
|
||||||
|
Hostname: "NewHostName.Local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostinfo-exists-automatic-givenName",
|
||||||
|
nodeBefore: Node{
|
||||||
|
GivenName: "automaticname.test",
|
||||||
|
Hostname: "AutomaticName.Test",
|
||||||
|
},
|
||||||
|
change: &tailcfg.Hostinfo{
|
||||||
|
Hostname: "NewHostName.Local",
|
||||||
|
},
|
||||||
|
want: Node{
|
||||||
|
GivenName: "newhostname.local",
|
||||||
|
Hostname: "NewHostName.Local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.nodeBefore.ApplyHostnameFromHostInfo(tt.change)
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.want, tt.nodeBefore, util.Comparers...); diff != "" {
|
||||||
|
t.Errorf("Patch unexpected result (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyPeerChange(t *testing.T) {
|
func TestApplyPeerChange(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
"github.com/juanfont/headscale/integration/hsic"
|
"github.com/juanfont/headscale/integration/hsic"
|
||||||
"github.com/juanfont/headscale/integration/tsic"
|
"github.com/juanfont/headscale/integration/tsic"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -654,6 +656,134 @@ func TestTaildrop(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateHostnameFromClient(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
user := "update-hostname-from-client"
|
||||||
|
|
||||||
|
hostnames := map[string]string{
|
||||||
|
"1": "user1-host",
|
||||||
|
"2": "User2-Host",
|
||||||
|
"3": "user3-host",
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario, err := NewScenario(dockertestMaxWait())
|
||||||
|
assertNoErrf(t, "failed to create scenario: %s", err)
|
||||||
|
defer scenario.ShutdownAssertNoPanics(t)
|
||||||
|
|
||||||
|
spec := map[string]int{
|
||||||
|
user: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("updatehostname"))
|
||||||
|
assertNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
assertNoErrListClients(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
headscale, err := scenario.Headscale()
|
||||||
|
assertNoErrGetHeadscale(t, err)
|
||||||
|
|
||||||
|
// update hostnames using the up command
|
||||||
|
for _, client := range allClients {
|
||||||
|
status, err := client.Status()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"tailscale",
|
||||||
|
"set",
|
||||||
|
"--hostname=" + hostnames[string(status.Self.ID)],
|
||||||
|
}
|
||||||
|
_, _, err = client.Execute(command)
|
||||||
|
assertNoErrf(t, "failed to set hostname: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
var nodes []*v1.Node
|
||||||
|
err = executeAndUnmarshal(
|
||||||
|
headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"node",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
&nodes,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNoErr(t, err)
|
||||||
|
assert.Len(t, nodes, 3)
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
hostname := hostnames[strconv.FormatUint(node.GetId(), 10)]
|
||||||
|
assert.Equal(t, hostname, node.GetName())
|
||||||
|
assert.Equal(t, util.ConvertWithFQDNRules(hostname), node.GetGivenName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename givenName in nodes
|
||||||
|
for _, node := range nodes {
|
||||||
|
givenName := fmt.Sprintf("%d-givenname", node.GetId())
|
||||||
|
_, err = headscale.Execute(
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"node",
|
||||||
|
"rename",
|
||||||
|
givenName,
|
||||||
|
"--identifier",
|
||||||
|
strconv.FormatUint(node.GetId(), 10),
|
||||||
|
})
|
||||||
|
assertNoErr(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
// Verify that the clients can see the new hostname, but no givenName
|
||||||
|
for _, client := range allClients {
|
||||||
|
status, err := client.Status()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"tailscale",
|
||||||
|
"set",
|
||||||
|
"--hostname=" + hostnames[string(status.Self.ID)] + "NEW",
|
||||||
|
}
|
||||||
|
_, _, err = client.Execute(command)
|
||||||
|
assertNoErrf(t, "failed to set hostname: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
err = executeAndUnmarshal(
|
||||||
|
headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"node",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
&nodes,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNoErr(t, err)
|
||||||
|
assert.Len(t, nodes, 3)
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
hostname := hostnames[strconv.FormatUint(node.GetId(), 10)]
|
||||||
|
givenName := fmt.Sprintf("%d-givenname", node.GetId())
|
||||||
|
assert.Equal(t, hostname+"NEW", node.GetName())
|
||||||
|
assert.Equal(t, givenName, node.GetGivenName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExpireNode(t *testing.T) {
|
func TestExpireNode(t *testing.T) {
|
||||||
IntegrationSkip(t)
|
IntegrationSkip(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
Loading…
Reference in a new issue