mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
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:
co-authored by
Pasha Sviderski
Anton Ovchinnikov
parent
6fb07db4b2
commit
da3634b690
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,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,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,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,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() {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,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,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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cluster
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ucind
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user