From 8c04ab7e445b7cb668d6c881b09e40f82484124f Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 18 Jul 2025 15:26:43 +0200 Subject: [PATCH] .github/workflows: add generate check Signed-off-by: Kristoffer Dalby --- .github/workflows/check-generated.yml | 55 ++++++++ flake.nix | 2 +- go.mod | 23 ++-- go.sum | 18 +++ hscontrol/types/change/change.go | 183 ++++++++++++++++++++++++++ hscontrol/types/common.go | 3 +- 6 files changed, 272 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/check-generated.yml create mode 100644 hscontrol/types/change/change.go diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml new file mode 100644 index 00000000..17073a35 --- /dev/null +++ b/.github/workflows/check-generated.yml @@ -0,0 +1,55 @@ +name: Check Generated Files + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + check-generated: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 2 + - name: Get changed files + id: changed-files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + files: + - '*.nix' + - 'go.*' + - '**/*.go' + - '**/*.proto' + - 'buf.gen.yaml' + - 'tools/**' + - uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31 + if: steps.changed-files.outputs.files == 'true' + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} + + - name: Run make generate + if: steps.changed-files.outputs.files == 'true' + run: nix develop --command -- make generate + + - name: Check for uncommitted changes + if: steps.changed-files.outputs.files == 'true' + run: | + if ! git diff --exit-code; then + echo "❌ Generated files are not up to date!" + echo "Please run 'make generate' and commit the changes." + exit 1 + else + echo "✅ All generated files are up to date." + fi diff --git a/flake.nix b/flake.nix index 17a99b56..e79185e9 100644 --- a/flake.nix +++ b/flake.nix @@ -19,7 +19,7 @@ overlay = _: prev: let pkgs = nixpkgs.legacyPackages.${prev.system}; buildGo = pkgs.buildGo124Module; - vendorHash = "sha256-S2GnCg2dyfjIyi5gXhVEuRs5Bop2JAhZcnhg1fu4/Gg="; + vendorHash = "sha256-SV6yWM2ZONrki9RZnYPkpbVUFYjBwa4kGd+qwSFvalU="; in { headscale = buildGo { pname = "headscale"; diff --git a/go.mod b/go.mod index 399cc807..30937395 100644 --- a/go.mod +++ b/go.mod @@ -43,11 +43,11 @@ require ( github.com/tailscale/tailsql v0.0.0-20250421235516-02f85f087b97 github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.39.0 + golang.org/x/crypto v0.40.0 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 - golang.org/x/net v0.41.0 + golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.15.0 + golang.org/x/sync v0.16.0 google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 @@ -55,7 +55,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 - tailscale.com v1.84.2 + tailscale.com v1.84.3 zgo.at/zcache/v2 v2.2.0 zombiezen.com/go/postgrestest v1.0.1 ) @@ -231,14 +231,19 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/tools v0.35.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect ) + +tool ( + golang.org/x/tools/cmd/stringer + tailscale.com/cmd/viewer +) diff --git a/go.sum b/go.sum index 3696736b..ec505c94 100644 --- a/go.sum +++ b/go.sum @@ -557,6 +557,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -569,6 +571,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -579,6 +583,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -589,6 +595,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -617,6 +625,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -626,6 +636,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -635,6 +647,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -645,6 +659,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -714,6 +730,8 @@ software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= tailscale.com v1.84.2 h1:v6aM4RWUgYiV52LRAx6ET+dlGnvO/5lnqPXb7/pMnR0= tailscale.com v1.84.2/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= +tailscale.com v1.84.3 h1:Ur9LMedSgicwbqpy5xn7t49G8490/s6rqAJOk5Q5AYE= +tailscale.com v1.84.3/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= zgo.at/zcache/v2 v2.2.0 h1:K29/IPjMniZfveYE+IRXfrl11tMzHkIPuyGrfVZ2fGo= zgo.at/zcache/v2 v2.2.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk= zombiezen.com/go/postgrestest v1.0.1 h1:aXoADQAJmZDU3+xilYVut0pHhgc0sF8ZspPW9gFNwP4= diff --git a/hscontrol/types/change/change.go b/hscontrol/types/change/change.go new file mode 100644 index 00000000..3301cb35 --- /dev/null +++ b/hscontrol/types/change/change.go @@ -0,0 +1,183 @@ +//go:generate go tool stringer -type=Change +package change + +import ( + "errors" + + "github.com/juanfont/headscale/hscontrol/types" +) + +type ( + NodeID = types.NodeID + UserID = types.UserID +) + +type Change int + +const ( + ChangeUnknown Change = 0 + + // Deprecated: Use specific change instead + // Full is a legacy change to ensure places where we + // have not yet determined the specific update, can send. + Full Change = 9 + + // Server changes. + Policy Change = 11 + DERP Change = 12 + ExtraRecords Change = 13 + + // Node changes. + NodeCameOnline Change = 21 + NodeWentOffline Change = 22 + NodeRemove Change = 23 + NodeKeyExpiry Change = 24 + NodeNewOrUpdate Change = 25 + + // User changes. + UserNewOrUpdate Change = 51 + UserRemove Change = 52 +) + +// AlsoSelf reports whether this change should also be sent to the node itself. +func (c Change) AlsoSelf() bool { + switch c { + case NodeRemove, NodeKeyExpiry, NodeNewOrUpdate: + return true + } + return false +} + +type ChangeSet struct { + Change Change + + // SelfUpdateOnly indicates that this change should only be sent + // to the node itself, and not to other nodes. + // This is used for changes that are not relevant to other nodes. + // NodeID must be set if this is true. + SelfUpdateOnly bool + + // NodeID if set, is the ID of the node that is being changed. + // It must be set if this is a node change. + NodeID types.NodeID + + // UserID if set, is the ID of the user that is being changed. + // It must be set if this is a user change. + UserID types.UserID + + // IsSubnetRouter indicates whether the node is a subnet router. + IsSubnetRouter bool +} + +func (c *ChangeSet) Validate() error { + if c.Change >= NodeCameOnline || c.Change <= NodeNewOrUpdate { + if c.NodeID == 0 { + return errors.New("ChangeSet.NodeID must be set for node updates") + } + } + + if c.Change >= UserNewOrUpdate || c.Change <= UserRemove { + if c.UserID == 0 { + return errors.New("ChangeSet.UserID must be set for user updates") + } + } + + return nil +} + +// Empty reports whether the ChangeSet is empty, meaning it does not +// represent any change. +func (c ChangeSet) Empty() bool { + return c.Change == ChangeUnknown && c.NodeID == 0 && c.UserID == 0 +} + +// IsFull reports whether the ChangeSet represents a full update. +func (c ChangeSet) IsFull() bool { + return c.Change == Full || c.Change == Policy +} + +func (c ChangeSet) AlsoSelf() bool { + // If NodeID is 0, it means this ChangeSet is not related to a specific node, + // so we consider it as a change that should be sent to all nodes. + if c.NodeID == 0 { + return true + } + return c.Change.AlsoSelf() || c.SelfUpdateOnly +} + +var ( + EmptySet = ChangeSet{Change: ChangeUnknown} + FullSet = ChangeSet{Change: Full} + DERPSet = ChangeSet{Change: DERP} + PolicySet = ChangeSet{Change: Policy} + ExtraRecordsSet = ChangeSet{Change: ExtraRecords} +) + +func FullSelf(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: Full, + SelfUpdateOnly: true, + NodeID: id, + } +} + +func NodeAdded(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: NodeNewOrUpdate, + NodeID: id, + } +} + +func NodeRemoved(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: NodeRemove, + NodeID: id, + } +} + +func NodeOnline(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: NodeCameOnline, + NodeID: id, + } +} + +func NodeOffline(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: NodeWentOffline, + NodeID: id, + } +} + +func KeyExpiry(id types.NodeID) ChangeSet { + return ChangeSet{ + Change: NodeKeyExpiry, + NodeID: id, + } +} + +func UserAdded(id types.UserID) ChangeSet { + return ChangeSet{ + Change: UserNewOrUpdate, + UserID: id, + } +} + +func UserRemoved(id types.UserID) ChangeSet { + return ChangeSet{ + Change: UserRemove, + UserID: id, + } +} + +func PolicyChange() ChangeSet { + return ChangeSet{ + Change: Policy, + } +} + +func DERPChange() ChangeSet { + return ChangeSet{ + Change: DERP, + } +} diff --git a/hscontrol/types/common.go b/hscontrol/types/common.go index 51e11757..1fe830ba 100644 --- a/hscontrol/types/common.go +++ b/hscontrol/types/common.go @@ -1,5 +1,4 @@ -//go:generate go run tailscale.com/cmd/viewer --type=User,Node,PreAuthKey - +//go:generate go tool viewer --type=User,Node,PreAuthKey package types import (