Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# yaml-language-server: $schema=https://taskfile.dev/schema.json

version: '3'

tasks:
default:
desc: List available tasks
cmds:
- task --list
silent: true

test:
desc: Run generation, tests, datastore integration tests, vet, and builds
cmds:
- task: test:generate
- task: test:unit
- task: test:race
- task: test:datastores
- task: test:vet
- task: test:build
- git diff --check

test:generate:
internal: true
cmd: go generate ./...

test:unit:
internal: true
cmd: go test ./...

test:race:
internal: true
cmd: go test -race ./west/... ./westport/...

test:datastores:
internal: true
preconditions:
- sh: docker version >/dev/null
msg: Docker is required for PostgreSQL and MySQL integration tests
cmd: |
set -eu

postgres_container="west-test-postgres-$$"
mysql_container="west-test-mysql-$$"
cleanup() {
docker rm -f "$postgres_container" "$mysql_container" >/dev/null 2>&1 || true
}
trap cleanup EXIT

docker run -d --rm \
--name "$postgres_container" \
-e POSTGRES_USER=west \
-e POSTGRES_PASSWORD=westpass \
-e POSTGRES_DB=west \
-p 127.0.0.1::5432 \
postgres:18-alpine >/dev/null

docker run -d --rm \
--name "$mysql_container" \
-e MYSQL_USER=west \
-e MYSQL_PASSWORD=westpass \
-e MYSQL_DATABASE=west \
-e MYSQL_ROOT_PASSWORD=rootpass \
-p 127.0.0.1::3306 \
mysql:8.4 >/dev/null

attempts=0
until docker exec "$postgres_container" pg_isready -U west -d west >/dev/null 2>&1; do
attempts=$((attempts + 1))
if [ "$attempts" -ge 60 ]; then
echo "PostgreSQL did not become ready" >&2
exit 1
fi
sleep 1
done

attempts=0
until docker exec "$mysql_container" mysqladmin ping -h127.0.0.1 -uroot -prootpass >/dev/null 2>&1; do
attempts=$((attempts + 1))
if [ "$attempts" -ge 60 ]; then
echo "MySQL did not become ready" >&2
exit 1
fi
sleep 1
done

postgres_address=$(docker port "$postgres_container" 5432)
mysql_address=$(docker port "$mysql_container" 3306)
WEST_TEST_POSTGRES_DSN="postgres://west:westpass@${postgres_address}/west?sslmode=disable" \
WEST_TEST_MYSQL_DSN="mysql://west:westpass@${mysql_address}/west" \
go test ./westport/db -run '^TestDatastoreSchema$' -count=1

test:vet:
internal: true
cmd: go vet ./...

test:build:
internal: true
cmd: go build ./cmd/west ./cmd/snap
2 changes: 2 additions & 0 deletions cmd/snap/snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/sprisa/west/west"
"github.com/sprisa/west/westport/db"
"github.com/sprisa/west/westport/localconfig"
"github.com/sprisa/x/env"
"github.com/sprisa/x/errutil"
l "github.com/sprisa/x/log"
Expand All @@ -22,6 +23,7 @@ func init() {
// The db should be located under the user common dir, not root
// Use snap common dir for configs
db.DBFilePath = filepath.Join(configDirPath, db.DBFilePath)
localconfig.FilePath = filepath.Join(configDirPath, localconfig.FilePath)
}

func main() {
Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/sprisa/west

go 1.25.6
go 1.27.0

tool (
github.com/99designs/gqlgen
Expand All @@ -16,9 +16,11 @@ require (
github.com/anandvarma/namegen v1.1.1
github.com/cqroot/prompt v0.9.4
github.com/go-acme/lego/v4 v4.26.0
github.com/go-sql-driver/mysql v1.10.0
github.com/goccy/go-yaml v1.19.2
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jackc/pgx/v5 v5.10.0
github.com/miekg/dns v1.1.68
github.com/rs/zerolog v1.34.0
github.com/samber/lo v1.52.0
Expand All @@ -39,6 +41,7 @@ require (

require (
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/alexflint/go-arg v1.5.1 // indirect
Expand Down Expand Up @@ -76,6 +79,9 @@ require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ entgo.io/contrib v0.7.0 h1:4Ghx8O0rqSMmca3FIJ6QyZbQAoLvdzWqLMl1MbHFEEw=
entgo.io/contrib v0.7.0/go.mod h1:zbPSUrbn+6dfyv8S9HWEvn1MyGpO95ik2lUNgaqWTt4=
entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/99designs/gqlgen v0.17.81 h1:kCkN/xVyRb5rEQpuwOHRTYq83i0IuTQg9vdIiwEerTs=
github.com/99designs/gqlgen v0.17.81/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
Expand Down Expand Up @@ -102,6 +104,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
Expand Down Expand Up @@ -149,6 +153,14 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down
8 changes: 4 additions & 4 deletions util/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package auth
import "github.com/golang-jwt/jwt/v5"

type TokenClaims struct {
Endpoint string `json:"endpoint"`
IP string `json:"ip"`
Ca string `json:"ca"`
PortIP string `json:"port_ip"`
Endpoint string `json:"endpoint"`
EndpointAddresses []string `json:"endpoint_addresses"`
IP string `json:"ip"`
Ca string `json:"ca"`
jwt.RegisteredClaims
}
10 changes: 5 additions & 5 deletions util/ipconv/ipconv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestIPv4ToInt(t *testing.T) {
} {
got, err := IPv4ToInt(c.in)
if got != c.want || err != nil {
t.Errorf("IPv4ToInt(%q) == %q, want %q", c.in, got, c.want)
t.Errorf("IPv4ToInt(%q) == %v, want %v", c.in, got, c.want)
}
}
}
Expand All @@ -32,7 +32,7 @@ func TestIPv4ToIntError(t *testing.T) {
} {
got, err := IPv4ToInt(c.in)
if err == nil {
t.Errorf("IPv4ToInt(%q) == %q, want %q", c.in, got, c.want)
t.Errorf("IPv4ToInt(%q) == %v, want %v", c.in, got, c.want)
}
}
}
Expand All @@ -48,7 +48,7 @@ func TestIPv6ToInt(t *testing.T) {
} {
got, err := IPv6ToInt(c.in)
if got != c.want || err != nil {
t.Errorf("IPv6ToInt(%q) == %q, want %q", c.in.To16(), got, c.want)
t.Errorf("IPv6ToInt(%q) == %v, want %v", c.in.To16(), got, c.want)
}
}
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestIntToIPv4(t *testing.T) {
} {
got := IntToIPv4(c.in)
if !got.Equal(c.want) {
t.Errorf("IntToIPv4(%q) == %q, want %q", c.in, got, c.want)
t.Errorf("IntToIPv4(%v) == %q, want %q", c.in, got, c.want)
}
}
}
Expand All @@ -101,7 +101,7 @@ func TestIntToIPv6(t *testing.T) {
} {
got := IntToIPv6(c.in[0], c.in[1])
if !got.Equal(c.want) {
t.Errorf("IntToIPv6(%q) == %q, want %q", c.in, got, c.want)
t.Errorf("IntToIPv6(%v) == %q, want %q", c.in, got, c.want)
}
}
}
Expand Down
36 changes: 31 additions & 5 deletions west/gql/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions west/gql/west.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ mutation ProvisionDevice($input: ProvisionDeviceInput!) {
cert
key
networkCipher
lighthouses {
overlayIp
endpoint
}
}
}
55 changes: 55 additions & 0 deletions west/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package west

import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"time"

"github.com/Khan/genqlient/graphql"
"github.com/sprisa/west/west/gql"
)

func provisionWithFailover(ctx context.Context, endpoint *url.URL, addresses []string, input gql.ProvisionDeviceInput) (*gql.ProvisionDeviceResponse, error) {
perAttempt := 10 * time.Second
var errs error

for _, addr := range addresses {
attemptCtx, cancel := context.WithTimeout(ctx, perAttempt)
httpClient := httpClientForAddress(endpoint, addr)
client := graphql.NewClient(endpoint.String(), httpClient)
data, err := gql.ProvisionDevice(attemptCtx, client, input)
cancel()
if err == nil {
return data, nil
}
errs = errors.Join(errs, err)
}

attemptCtx, cancel := context.WithTimeout(ctx, perAttempt)
defer cancel()
client := graphql.NewClient(endpoint.String(), &http.Client{Timeout: perAttempt})
data, err := gql.ProvisionDevice(attemptCtx, client, input)
if err == nil {
return data, nil
}
return nil, errors.Join(errs, err)
}

func httpClientForAddress(endpoint *url.URL, address string) *http.Client {
dialer := &net.Dialer{Timeout: 5 * time.Second}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
}
if endpoint.Scheme == "https" {
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{}
}
transport.TLSClientConfig.ServerName = endpoint.Hostname()
}
return &http.Client{Transport: transport, Timeout: 10 * time.Second}
}
Loading