E2E tests use repeated strings for test data where constants add no value (#97)

---------

Co-authored-by: Pasha Sviderski <me@psviderski.name>
Co-authored-by: Anton Ovchinnikov <anton@tonyo.info>
This commit is contained in:
Evgenii Orlov
2025-07-21 19:30:02 +02:00
committed by GitHub
co-authored by Pasha Sviderski Anton Ovchinnikov
parent 6fb07db4b2
commit da3634b690
56 changed files with 170 additions and 116 deletions
+5 -5
View File
@@ -25,15 +25,15 @@ jobs:
with: with:
go-version: "1.23.2" go-version: "1.23.2"
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.2.2
- name: Format code - name: Format code
run: | run: |
make format make format
git diff --exit-code || git diff --exit-code ||
(echo "Code is not formatted. Please run 'make format' and commit the changes." && exit 1) (echo "Code is not formatted. Please run 'make format' and commit the changes." && exit 1)
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.2.2
timeout-minutes: 10 timeout-minutes: 10
+14 -2
View File
@@ -1,4 +1,10 @@
version: "2" version: "2"
run:
concurrency: 4
tests: true
timeout: 5m
linters: linters:
default: none default: none
enable: enable:
@@ -23,8 +29,14 @@ linters:
# - unparam # - unparam
# - unused # - unused
- whitespace - whitespace
exclusions:
rules:
- path: ^test/e2e
linters:
- goconst # constants here add no value, so we skip goconst only for test/e2e.
formatters: formatters:
enable: enable:
- gofmt - gofumpt
# - goimports - goimports
+6 -4
View File
@@ -89,9 +89,9 @@ test-clean:
vet: vet:
go vet ./... go vet ./...
.PHONY: format .PHONY: format fmt
format: format fmt:
go fmt ./... GOOS=linux golangci-lint fmt
LINT_TARGETS := lint lint-and-fix LINT_TARGETS := lint lint-and-fix
.PHONY: $(LINT_TARGETS) _lint .PHONY: $(LINT_TARGETS) _lint
@@ -99,7 +99,9 @@ $(LINT_TARGETS): _lint
lint: ARGS= lint: ARGS=
lint-and-fix: ARGS=--fix lint-and-fix: ARGS=--fix
_lint: _lint:
golangci-lint run $(ARGS) # Explicitly set OS to Linux to not skip *_linux.go files when running on macOS.
# Uncloud daemon won't likely support OS other than Linux anytime soon, so for now we can rely on that.
GOOS=linux golangci-lint run $(ARGS)
.PHONY: docs-image-push .PHONY: docs-image-push
docs-image-push: docs-image-push:
+1
View File
@@ -2,6 +2,7 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/ucind" "github.com/psviderski/uncloud/internal/ucind"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
+1
View File
@@ -2,6 +2,7 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/ucind" "github.com/psviderski/uncloud/internal/ucind"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
+1 -1
View File
@@ -13,7 +13,7 @@ func NewRootCommand() *cobra.Command {
} }
cmd.AddCommand( cmd.AddCommand(
NewCreateCommand(), NewCreateCommand(),
//NewListCommand(), // NewListCommand(),
NewRemoveCommand(), NewRemoveCommand(),
) )
return cmd return cmd
+2 -3
View File
@@ -101,7 +101,6 @@ func remove(ctx context.Context, uncli *cli.CLI, machineName string, opts remove
err = progress.RunWithTitle(ctx, func(ctx context.Context) error { err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
return removeContainers(ctx, client, containers) return removeContainers(ctx, client, containers)
}, uncli.ProgressOut(), "Removing containers") }, uncli.ProgressOut(), "Removing containers")
if err != nil { if err != nil {
return fmt.Errorf("remove containers: %w", err) return fmt.Errorf("remove containers: %w", err)
} }
@@ -112,8 +111,8 @@ func remove(ctx context.Context, uncli *cli.CLI, machineName string, opts remove
// TODO: 5. Remove the machine from the cluster store. // TODO: 5. Remove the machine from the cluster store.
return fmt.Errorf("resetting machine is not fully implemented yet") return fmt.Errorf("resetting machine is not fully implemented yet")
//fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name) // fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
//return nil // return nil
} }
// formatContainerTree formats a list of containers grouped by service as a tree structure. // formatContainerTree formats a list of containers grouped by service as a tree structure.
+1
View File
@@ -2,6 +2,7 @@ package machine
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/daemon" "github.com/psviderski/uncloud/internal/daemon"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
"github.com/spf13/cobra" "github.com/spf13/cobra"
-1
View File
@@ -9,7 +9,6 @@ import (
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/psviderski/uncloud/internal/cli" "github.com/psviderski/uncloud/internal/cli"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
+3 -2
View File
@@ -3,10 +3,11 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/hashicorp/serf/serf"
crdt "github.com/ipfs/go-ds-crdt"
"log/slog" "log/slog"
"time" "time"
"github.com/hashicorp/serf/serf"
crdt "github.com/ipfs/go-ds-crdt"
) )
// Implements the Broadcaster interface. // Implements the Broadcaster interface.
+2 -1
View File
@@ -2,9 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/dgraph-io/badger/v3"
"log" "log"
"time" "time"
"github.com/dgraph-io/badger/v3"
) )
func customTimeEncoder(t time.Time) string { func customTimeEncoder(t time.Time) string {
+2 -1
View File
@@ -2,9 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/ipfs/go-log/v2"
"log/slog" "log/slog"
"os" "os"
"github.com/ipfs/go-log/v2"
) )
// ipfsLogger is an slog logger that implements the IPFS go-log StandardLogger interface. // ipfsLogger is an slog logger that implements the IPFS go-log StandardLogger interface.
+2 -1
View File
@@ -2,12 +2,13 @@ package main
import ( import (
"fmt" "fmt"
"github.com/docker/docker/libnetwork/networkdb"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"github.com/docker/docker/libnetwork/networkdb"
) )
func main() { func main() {
+13 -12
View File
@@ -4,6 +4,13 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/hashicorp/memberlist" "github.com/hashicorp/memberlist"
"github.com/hashicorp/serf/cmd/serf/command/agent" "github.com/hashicorp/serf/cmd/serf/command/agent"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
@@ -11,12 +18,6 @@ import (
badger "github.com/ipfs/go-ds-badger3" badger "github.com/ipfs/go-ds-badger3"
crdt "github.com/ipfs/go-ds-crdt" crdt "github.com/ipfs/go-ds-crdt"
"github.com/lmittmann/tint" "github.com/lmittmann/tint"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"time"
) )
func createSerfAgentConfig(name, bindAddr, rpcAddr, profile string) *agent.Config { func createSerfAgentConfig(name, bindAddr, rpcAddr, profile string) *agent.Config {
@@ -49,9 +50,9 @@ func createSerfAgent(config *agent.Config) (*agent.Agent, error) {
serfConfig.MemberlistConfig.BindAddr = bindIP serfConfig.MemberlistConfig.BindAddr = bindIP
serfConfig.MemberlistConfig.BindPort = bindPort serfConfig.MemberlistConfig.BindPort = bindPort
//serfConfig.MemberlistConfig.AdvertiseAddr = advertiseIP // serfConfig.MemberlistConfig.AdvertiseAddr = advertiseIP
//serfConfig.MemberlistConfig.AdvertisePort = advertisePort // serfConfig.MemberlistConfig.AdvertisePort = advertisePort
//serfConfig.MemberlistConfig.SecretKey = encryptKey // serfConfig.MemberlistConfig.SecretKey = encryptKey
serfConfig.NodeName = config.NodeName serfConfig.NodeName = config.NodeName
serfConfig.Tags = config.Tags serfConfig.Tags = config.Tags
serfConfig.SnapshotPath = config.SnapshotPath serfConfig.SnapshotPath = config.SnapshotPath
@@ -129,7 +130,7 @@ func main() {
logger := slog.New(tint.NewHandler(os.Stdout, &tint.Options{ logger := slog.New(tint.NewHandler(os.Stdout, &tint.Options{
AddSource: true, AddSource: true,
Level: slog.LevelDebug, Level: slog.LevelDebug,
//Level: slog.LevelInfo, // Level: slog.LevelInfo,
TimeFormat: time.RFC3339Nano, TimeFormat: time.RFC3339Nano,
})) }))
slog.SetDefault(logger) slog.SetDefault(logger)
@@ -164,7 +165,7 @@ func main() {
opts := crdt.DefaultOptions() opts := crdt.DefaultOptions()
opts.Logger = newIPFSLogger(logger) opts.Logger = newIPFSLogger(logger)
//opts.MultiHeadProcessing = true // opts.MultiHeadProcessing = true
// TODO: debug why the heads count may grow on the receiving side if the event backlog is huge and the processing // TODO: debug why the heads count may grow on the receiving side if the event backlog is huge and the processing
// is slow. // is slow.
store, err := crdt.New(localStore, ds.NewKey("/"), syncer, broadcaster, opts) store, err := crdt.New(localStore, ds.NewKey("/"), syncer, broadcaster, opts)
@@ -172,7 +173,7 @@ func main() {
panic(err) panic(err)
} }
//ticker := time.NewTicker(10 * time.Millisecond) // ticker := time.NewTicker(10 * time.Millisecond)
ticker := time.NewTicker(3 * time.Second) ticker := time.NewTicker(3 * time.Second)
go func() { go func() {
for { for {
+3 -2
View File
@@ -6,12 +6,13 @@ import (
"crypto/cipher" "crypto/cipher"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/netip"
"time"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
"github.com/siderolabs/discovery-api/api/v1alpha1/client/pb" "github.com/siderolabs/discovery-api/api/v1alpha1/client/pb"
discovery "github.com/siderolabs/discovery-client/pkg/client" discovery "github.com/siderolabs/discovery-client/pkg/client"
"go.uber.org/zap" "go.uber.org/zap"
"net/netip"
"time"
) )
const ( const (
+2 -2
View File
@@ -53,11 +53,11 @@ func (c *Config) Read() error {
func (c *Config) Save() error { func (c *Config) Save() error {
dir, _ := filepath.Split(c.path) dir, _ := filepath.Split(c.path)
if err := os.MkdirAll(dir, 0700); err != nil { if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("create config directory '%s': %w", dir, err) return fmt.Errorf("create config directory '%s': %w", dir, err)
} }
f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil { if err != nil {
return fmt.Errorf("write config file '%s': %w", c.path, err) return fmt.Errorf("write config file '%s': %w", c.path, err)
} }
+3 -2
View File
@@ -5,14 +5,15 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/cenkalti/backoff/v4"
"golang.org/x/net/http2"
"log/slog" "log/slog"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"net/url" "net/url"
"time" "time"
"github.com/cenkalti/backoff/v4"
"golang.org/x/net/http2"
) )
const ( const (
+2 -1
View File
@@ -6,11 +6,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/cenkalti/backoff/v4"
"io" "io"
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
"github.com/cenkalti/backoff/v4"
) )
type ChangeType string type ChangeType string
+2 -1
View File
@@ -3,9 +3,10 @@ package daemon
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
systemd "github.com/coreos/go-systemd/daemon" systemd "github.com/coreos/go-systemd/daemon"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
"log/slog"
) )
type Daemon struct { type Daemon struct {
+3 -2
View File
@@ -3,10 +3,11 @@ package daemon
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/network"
"net/netip" "net/netip"
"os" "os"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/network"
) )
// MachineToken returns the local machine's token that can be used for adding the machine to a cluster. // MachineToken returns the local machine's token that can be used for adding the machine to a cluster.
+3 -2
View File
@@ -4,10 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/cenkalti/backoff/v4"
"github.com/docker/docker/client"
"log/slog" "log/slog"
"time" "time"
"github.com/cenkalti/backoff/v4"
"github.com/docker/docker/client"
) )
// WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests. // WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests.
+1
View File
@@ -2,6 +2,7 @@ package proxy
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/encoding/protowire"
+2 -1
View File
@@ -2,11 +2,12 @@ package proxy
import ( import (
"context" "context"
"sync"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"sync"
) )
// Director manages routing of gRPC requests between local and remote backends. // Director manages routing of gRPC requests between local and remote backends.
+2 -1
View File
@@ -2,11 +2,12 @@ package proxy
import ( import (
"context" "context"
"sync"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"sync"
) )
// LocalBackend is a proxy.One2ManyResponder implementation that proxies to a local gRPC server listening on a Unix socket. // LocalBackend is a proxy.One2ManyResponder implementation that proxies to a local gRPC server listening on a Unix socket.
+4 -3
View File
@@ -3,14 +3,15 @@ package proxy
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip"
"sync"
"time"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/backoff" "google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"net/netip"
"sync"
"time"
) )
// RemoteBackend is a proxy.One2ManyResponder implementation that proxies to a remote gRPC server, injecting machine metadata // RemoteBackend is a proxy.One2ManyResponder implementation that proxies to a remote gRPC server, injecting machine metadata
+2 -2
View File
@@ -29,7 +29,7 @@ type Controller struct {
func NewController(store *store.Store, path string, verifyResponse string) (*Controller, error) { func NewController(store *store.Store, path string, verifyResponse string) (*Controller, error) {
dir := filepath.Dir(path) dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0750); err != nil { if err := os.MkdirAll(dir, 0o750); err != nil {
return nil, fmt.Errorf("create parent directory for Caddy configuration '%s': %w", dir, err) return nil, fmt.Errorf("create parent directory for Caddy configuration '%s': %w", dir, err)
} }
if err := fs.Chown(dir, "", CaddyGroup); err != nil { if err := fs.Chown(dir, "", CaddyGroup); err != nil {
@@ -114,7 +114,7 @@ func (c *Controller) generateConfig(containers []api.ServiceContainer) error {
return fmt.Errorf("marshal Caddy configuration: %w", err) return fmt.Errorf("marshal Caddy configuration: %w", err)
} }
if err = os.WriteFile(c.path, configBytes, 0640); err != nil { if err = os.WriteFile(c.path, configBytes, 0o640); err != nil {
return fmt.Errorf("write Caddy configuration to file '%s': %w", c.path, err) return fmt.Errorf("write Caddy configuration to file '%s': %w", c.path, err)
} }
if err = fs.Chown(c.path, "", CaddyGroup); err != nil { if err = fs.Chown(c.path, "", CaddyGroup); err != nil {
+4 -3
View File
@@ -5,6 +5,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"net/netip"
"time"
"github.com/psviderski/uncloud/internal/corrosion" "github.com/psviderski/uncloud/internal/corrosion"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
@@ -13,9 +17,6 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
"log/slog"
"net/netip"
"time"
) )
type Cluster struct { type Cluster struct {
+1
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/psviderski/uncloud/internal/dns" "github.com/psviderski/uncloud/internal/dns"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/store" "github.com/psviderski/uncloud/internal/machine/store"
+2 -1
View File
@@ -3,8 +3,9 @@ package cluster
import ( import (
"errors" "errors"
"fmt" "fmt"
"go4.org/netipx"
"net/netip" "net/netip"
"go4.org/netipx"
) )
const DefaultSubnetBits = 24 const DefaultSubnetBits = 24
+1
View File
@@ -2,6 +2,7 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/internal/secret"
) )
+6 -5
View File
@@ -3,11 +3,12 @@ package corroservice
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/BurntSushi/toml"
"github.com/psviderski/uncloud/internal/fs"
"net/netip" "net/netip"
"os" "os"
"path/filepath" "path/filepath"
"github.com/BurntSushi/toml"
"github.com/psviderski/uncloud/internal/fs"
) )
const ( const (
@@ -50,7 +51,7 @@ func (c *Config) Write(path, owner string) error {
if err := encoder.Encode(c); err != nil { if err := encoder.Encode(c); err != nil {
return fmt.Errorf("encode config: %w", err) return fmt.Errorf("encode config: %w", err)
} }
if err := os.WriteFile(path, data.Bytes(), 0600); err != nil { if err := os.WriteFile(path, data.Bytes(), 0o600); err != nil {
return err return err
} }
if err := fs.Chown(path, owner, owner); err != nil { if err := fs.Chown(path, owner, owner); err != nil {
@@ -62,10 +63,10 @@ func (c *Config) Write(path, owner string) error {
func MkDataDir(dir, owner string) error { func MkDataDir(dir, owner string) error {
parent, _ := filepath.Split(dir) parent, _ := filepath.Split(dir)
// Use 0711 for parent directories to allow `owner` to access its nested data directory. // Use 0711 for parent directories to allow `owner` to access its nested data directory.
if err := os.MkdirAll(parent, 0711); err != nil { if err := os.MkdirAll(parent, 0o711); err != nil {
return fmt.Errorf("create directory %q: %w", parent, err) return fmt.Errorf("create directory %q: %w", parent, err)
} }
if err := os.Mkdir(dir, 0700); err != nil { if err := os.Mkdir(dir, 0o700); err != nil {
if !os.IsExist(err) { if !os.IsExist(err) {
return fmt.Errorf("create directory %q: %w", dir, err) return fmt.Errorf("create directory %q: %w", dir, err)
} }
+5 -4
View File
@@ -3,15 +3,16 @@ package corroservice
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"log/slog"
"path/filepath"
"time"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"io"
"log/slog"
"path/filepath"
"time"
) )
const ( const (
+2 -2
View File
@@ -111,8 +111,8 @@ func (s *SubprocessService) startProcess(ctx context.Context) error {
// TODO: figure out the waiting process // TODO: figure out the waiting process
// Wait for initialization // Wait for initialization
//timer := time.NewTimer(2 * time.Second) // timer := time.NewTimer(2 * time.Second)
//defer timer.Stop() // defer timer.Stop()
//select { //select {
////case <-timer.C: ////case <-timer.C:
+2 -2
View File
@@ -14,13 +14,13 @@ const DBFileName = "machine.db"
func NewDB(path string) (*sqlx.DB, error) { func NewDB(path string) (*sqlx.DB, error) {
// Create the database file with 0600 permissions if it doesn't exist, or update permissions if exists. // Create the database file with 0600 permissions if it doesn't exist, or update permissions if exists.
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil { if err != nil {
return nil, fmt.Errorf("create SQLite database '%s': %w", path, err) return nil, fmt.Errorf("create SQLite database '%s': %w", path, err)
} }
file.Close() file.Close()
} else { } else {
if err = os.Chmod(path, 0600); err != nil { if err = os.Chmod(path, 0o600); err != nil {
return nil, fmt.Errorf("update SQLite database permissions '%s': %w", path, err) return nil, fmt.Errorf("update SQLite database permissions '%s': %w", path, err)
} }
} }
+2 -2
View File
@@ -501,7 +501,7 @@ func listenUnixSocket(path string) (net.Listener, error) {
// Ensure the parent directory exists and has the correct group permissions. // Ensure the parent directory exists and has the correct group permissions.
parent, _ := filepath.Split(path) parent, _ := filepath.Split(path)
if err = os.MkdirAll(parent, 0750); err != nil { if err = os.MkdirAll(parent, 0o750); err != nil {
return nil, fmt.Errorf("create directory %q: %w", parent, err) return nil, fmt.Errorf("create directory %q: %w", parent, err)
} }
if err = os.Chown(parent, -1, gid); err != nil { if err = os.Chown(parent, -1, gid); err != nil {
@@ -555,7 +555,7 @@ func (m *Machine) configureCorrosion() error {
return fmt.Errorf("write corrosion config: %w", err) return fmt.Errorf("write corrosion config: %w", err)
} }
if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil { if err := os.WriteFile(schemaPath, []byte(store.Schema), 0o644); err != nil {
return fmt.Errorf("write corrosion schema: %w", err) return fmt.Errorf("write corrosion schema: %w", err)
} }
+2 -1
View File
@@ -1,11 +1,12 @@
package network package network
import ( import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"log/slog" "log/slog"
"net/netip" "net/netip"
"slices" "slices"
"time" "time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
const ( const (
+4 -3
View File
@@ -3,13 +3,14 @@ package tunnel
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"net/netip"
"time"
"github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/internal/secret"
"golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun/netstack" "golang.zx2c4.com/wireguard/tun/netstack"
"net"
"net/netip"
"time"
) )
const ( const (
+3 -2
View File
@@ -2,10 +2,11 @@ package network
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/secret"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net/netip" "net/netip"
"time" "time"
"github.com/psviderski/uncloud/internal/secret"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
const ( const (
+7 -6
View File
@@ -6,18 +6,19 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/secret"
"github.com/vishvananda/netlink"
"go4.org/netipx"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"log/slog" "log/slog"
"net" "net"
"net/netip" "net/netip"
"slices" "slices"
"sync" "sync"
"time" "time"
"github.com/psviderski/uncloud/internal/secret"
"github.com/vishvananda/netlink"
"go4.org/netipx"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
type WireGuardNetwork struct { type WireGuardNetwork struct {
+4 -3
View File
@@ -3,10 +3,11 @@ package machine
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/machine/network"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/psviderski/uncloud/internal/machine/network"
) )
const ( const (
@@ -71,7 +72,7 @@ func (c *State) Save() error {
return fmt.Errorf("state path not set") return fmt.Errorf("state path not set")
} }
dir, _ := filepath.Split(c.path) dir, _ := filepath.Split(c.path)
if err := os.MkdirAll(dir, 0711); err != nil { if err := os.MkdirAll(dir, 0o711); err != nil {
return fmt.Errorf("create state directory %q: %w", dir, err) return fmt.Errorf("create state directory %q: %w", dir, err)
} }
@@ -79,5 +80,5 @@ func (c *State) Save() error {
if err != nil { if err != nil {
return err return err
} }
return os.WriteFile(c.path, data, 0600) return os.WriteFile(c.path, data, 0o600)
} }
+3 -2
View File
@@ -4,11 +4,12 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
sq "github.com/Masterminds/squirrel"
"github.com/psviderski/uncloud/pkg/api"
"log/slog" "log/slog"
"strings" "strings"
"time" "time"
sq "github.com/Masterminds/squirrel"
"github.com/psviderski/uncloud/pkg/api"
) )
const ( const (
+2 -1
View File
@@ -5,10 +5,11 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"github.com/psviderski/uncloud/internal/corrosion" "github.com/psviderski/uncloud/internal/corrosion"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"log/slog"
) )
var ( var (
+2 -1
View File
@@ -4,9 +4,10 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/secret"
"net/netip" "net/netip"
"strings" "strings"
"github.com/psviderski/uncloud/internal/secret"
) )
const ( const (
+2 -1
View File
@@ -3,9 +3,10 @@ package sshexec
import ( import (
"context" "context"
"fmt" "fmt"
"golang.org/x/crypto/ssh"
"io" "io"
"strings" "strings"
"golang.org/x/crypto/ssh"
) )
type Remote struct { type Remote struct {
+1
View File
@@ -2,6 +2,7 @@ package ucind
import ( import (
"errors" "errors"
"github.com/docker/docker/client" "github.com/docker/docker/client"
) )
+3 -2
View File
@@ -1,10 +1,11 @@
package api package api
import ( import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/netip" "net/netip"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPortSpec_Validate(t *testing.T) { func TestPortSpec_Validate(t *testing.T) {
+2 -1
View File
@@ -1,10 +1,11 @@
package client package client
import ( import (
"testing"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing"
) )
func TestLatestCaddyImage(t *testing.T) { func TestLatestCaddyImage(t *testing.T) {
+1 -1
View File
@@ -128,7 +128,7 @@ func convertServicePortConfigToPortSpec(port types.ServicePortConfig) (api.PortS
// convertStandardPortsToPortSpecs converts []types.ServicePortConfig directly to api.PortSpecs. // convertStandardPortsToPortSpecs converts []types.ServicePortConfig directly to api.PortSpecs.
func convertStandardPortsToPortSpecs(ports []types.ServicePortConfig) ([]api.PortSpec, error) { func convertStandardPortsToPortSpecs(ports []types.ServicePortConfig) ([]api.PortSpec, error) {
var specs = make([]api.PortSpec, 0, len(ports)) specs := make([]api.PortSpec, 0, len(ports))
for _, port := range ports { for _, port := range ports {
spec, err := convertServicePortConfigToPortSpec(port) spec, err := convertServicePortConfigToPortSpec(port)
+3 -2
View File
@@ -3,13 +3,14 @@ package connector
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"strings"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/sshexec" "github.com/psviderski/uncloud/internal/sshexec"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"net"
"strings"
) )
type SSHConnectorConfig struct { type SSHConnectorConfig struct {
+2 -1
View File
@@ -3,9 +3,10 @@ package connector
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"net/netip"
) )
// TCPConnector establishes a connection to the machine API through a direct TCP connection to an API endpoint. // TCPConnector establishes a connection to the machine API through a direct TCP connection to an API endpoint.
+4 -3
View File
@@ -3,6 +3,10 @@ package connector
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"net/netip"
"strconv"
"github.com/psviderski/uncloud/internal/cli/config" "github.com/psviderski/uncloud/internal/cli/config"
machine2 "github.com/psviderski/uncloud/internal/machine" machine2 "github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
@@ -10,9 +14,6 @@ import (
"github.com/psviderski/uncloud/pkg/client" "github.com/psviderski/uncloud/pkg/client"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"net"
"net/netip"
"strconv"
) )
// WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel // WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel
+5 -3
View File
@@ -11,9 +11,11 @@ import (
type ContainerSpecStatus string type ContainerSpecStatus string
const ContainerUpToDate ContainerSpecStatus = "up-to-date" const (
const ContainerNeedsUpdate ContainerSpecStatus = "needs-update" ContainerUpToDate ContainerSpecStatus = "up-to-date"
const ContainerNeedsRecreate ContainerSpecStatus = "needs-recreate" ContainerNeedsUpdate ContainerSpecStatus = "needs-update"
ContainerNeedsRecreate ContainerSpecStatus = "needs-recreate"
)
func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) ContainerSpecStatus { func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) ContainerSpecStatus {
current = current.SetDefaults() current = current.SetDefaults()
+5 -4
View File
@@ -4,6 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"sync"
"time"
"github.com/cenkalti/backoff/v4" "github.com/cenkalti/backoff/v4"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
@@ -11,10 +16,6 @@ import (
"github.com/psviderski/uncloud/pkg/api" "github.com/psviderski/uncloud/pkg/api"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"io"
"net/http"
"sync"
"time"
) )
// GetDomain returns the cluster domain name or ErrNotFound if it hasn't been reserved yet. // GetDomain returns the cluster domain name or ErrNotFound if it hasn't been reserved yet.
+2 -1
View File
@@ -2,10 +2,11 @@ package client
import ( import (
"fmt" "fmt"
"net/netip"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/internal/secret"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net/netip"
) )
type User struct { type User struct {
-1
View File
@@ -20,7 +20,6 @@ import (
"github.com/psviderski/uncloud/pkg/api" "github.com/psviderski/uncloud/pkg/api"
"github.com/psviderski/uncloud/pkg/client/compose" "github.com/psviderski/uncloud/pkg/client/compose"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
+6 -6
View File
@@ -259,8 +259,8 @@ func TestDeployment(t *testing.T) {
assert.Len(t, machines.ToSlice(), 3, "Expected 1 container on each machine") assert.Len(t, machines.ToSlice(), 3, "Expected 1 container on each machine")
// TODO: update the container spec in-place if only the placement constraint has changed. // TODO: update the container spec in-place if only the placement constraint has changed.
//containers = serviceContainerIDs(svc) // containers = serviceContainerIDs(svc)
//assert.True(t, initialContainers.IsSubset(containers), "Expected all initial containers to remain") // assert.True(t, initialContainers.IsSubset(containers), "Expected all initial containers to remain")
}) })
t.Run("caddy", func(t *testing.T) { t.Run("caddy", func(t *testing.T) {
@@ -334,7 +334,7 @@ func TestDeployment(t *testing.T) {
assertServiceMatchesSpec(t, svc, deployment.Spec) assertServiceMatchesSpec(t, svc, deployment.Spec)
assert.Equal(t, c.Machines[0].ID, svc.Containers[0].MachineID) assert.Equal(t, c.Machines[0].ID, svc.Containers[0].MachineID)
//initialContainerID := svc.Containers[0].Container.ID // initialContainerID := svc.Containers[0].Container.ID
// Deploy to all machines without a placement constraint. // Deploy to all machines without a placement constraint.
deployment, err = cli.NewCaddyDeployment(image, api.Placement{}) deployment, err = cli.NewCaddyDeployment(image, api.Placement{})
@@ -352,8 +352,8 @@ func TestDeployment(t *testing.T) {
machines := serviceMachines(svc) machines := serviceMachines(svc)
assert.Len(t, machines.ToSlice(), 3, "Expected 1 container on each machine") assert.Len(t, machines.ToSlice(), 3, "Expected 1 container on each machine")
// TODO: update the container spec in-place if only the placement constraint has changed. // TODO: update the container spec in-place if only the placement constraint has changed.
//containers := serviceContainerIDs(svc) // containers := serviceContainerIDs(svc)
//assert.True(t, containers.Contains(initialContainerID), "Expected initial container to remain") // assert.True(t, containers.Contains(initialContainerID), "Expected initial container to remain")
}) })
t.Run("replicated", func(t *testing.T) { t.Run("replicated", func(t *testing.T) {
@@ -587,7 +587,7 @@ func TestDeployment(t *testing.T) {
require.Error(t, err, "Deployment should fail when volume doesn't exist") require.Error(t, err, "Deployment should fail when volume doesn't exist")
require.Contains(t, err.Error(), "no machines available") require.Contains(t, err.Error(), "no machines available")
// TODO: implement and check for more details about the failed constraints. // TODO: implement and check for more details about the failed constraints.
//require.Contains(t, err.Error(), "volume 'non-existent-volume' not found") // require.Contains(t, err.Error(), "volume 'non-existent-volume' not found")
}) })
// Tests that when a volume exists on a single machine, all requested replicas will be deployed to that machine, // Tests that when a volume exists on a single machine, all requested replicas will be deployed to that machine,