mirror of
https://github.com/psviderski/uncloud.git
synced 2026-10-07 13:49:02 +00:00
Compare commits
1
Commits
main
..
hub-landing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e698f6d06e |
No files matched your search
@@ -2,7 +2,6 @@
|
|||||||
*
|
*
|
||||||
|
|
||||||
# Allow files and directories.
|
# Allow files and directories.
|
||||||
!api/
|
|
||||||
!cmd/
|
!cmd/
|
||||||
!internal/
|
!internal/
|
||||||
!pkg/
|
!pkg/
|
||||||
|
|||||||
@@ -18,6 +18,3 @@ indent_size = 4
|
|||||||
[*.sh]
|
[*.sh]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[release-notes-*.md]
|
|
||||||
max_line_length = off
|
|
||||||
@@ -57,8 +57,7 @@ jobs:
|
|||||||
|
|
||||||
The commands below show how to install the nightly version of uncloud.
|
The commands below show how to install the nightly version of uncloud.
|
||||||
|
|
||||||
> [!WARNING]
|
> **Warning**: These are nightly development builds and may be unstable.
|
||||||
> These are nightly development builds and may be unstable.
|
|
||||||
|
|
||||||
**Install uncloud CLI:**
|
**Install uncloud CLI:**
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,3 @@ node_modules/
|
|||||||
.devcontainer/
|
.devcontainer/
|
||||||
|
|
||||||
.zed/
|
.zed/
|
||||||
research/
|
|
||||||
+10
-19
@@ -20,9 +20,9 @@ snapshot:
|
|||||||
version_template: "{{ incminor .Version }}-nightly-{{.ShortCommit}}"
|
version_template: "{{ incminor .Version }}-nightly-{{.ShortCommit}}"
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: uc
|
- id: uncloud
|
||||||
main: ./cmd/uc
|
main: ./cmd/uncloud
|
||||||
binary: uc
|
binary: uncloud
|
||||||
env:
|
env:
|
||||||
# Use CGO to be able to build fsevent required by compose on macOS.
|
# Use CGO to be able to build fsevent required by compose on macOS.
|
||||||
- CGO_ENABLED={{ if eq .Os "darwin" }}1{{ else }}0{{ end }}
|
- CGO_ENABLED={{ if eq .Os "darwin" }}1{{ else }}0{{ end }}
|
||||||
@@ -34,12 +34,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w
|
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
|
||||||
-X github.com/psviderski/uncloud/internal/version.version={{.Version}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.commit={{.Commit}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.dirty={{.IsGitDirty}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.date={{.CommitDate}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.builtBy=goreleaser
|
|
||||||
|
|
||||||
- id: uncloudd
|
- id: uncloudd
|
||||||
main: ./cmd/uncloudd
|
main: ./cmd/uncloudd
|
||||||
@@ -52,17 +47,12 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w
|
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
|
||||||
-X github.com/psviderski/uncloud/internal/version.version={{.Version}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.commit={{.Commit}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.dirty={{.IsGitDirty}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.date={{.CommitDate}}
|
|
||||||
-X github.com/psviderski/uncloud/internal/version.builtBy=goreleaser
|
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: uc
|
- id: uncloud
|
||||||
builds:
|
builds:
|
||||||
- uc
|
- uncloud
|
||||||
formats:
|
formats:
|
||||||
# gz is not compatible with homebrew releases, so we use tar.gz
|
# gz is not compatible with homebrew releases, so we use tar.gz
|
||||||
- tar.gz
|
- tar.gz
|
||||||
@@ -103,9 +93,10 @@ brews:
|
|||||||
description: "Uncloud CLI"
|
description: "Uncloud CLI"
|
||||||
homepage: "https://uncloud.run"
|
homepage: "https://uncloud.run"
|
||||||
ids:
|
ids:
|
||||||
- uc
|
- uncloud
|
||||||
install: |
|
install: |
|
||||||
bin.install "uc"
|
bin.install "uncloud"
|
||||||
|
bin.install_symlink "uncloud" => "uc"
|
||||||
skip_upload: false
|
skip_upload: false
|
||||||
repository:
|
repository:
|
||||||
owner: psviderski
|
owner: psviderski
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ management.
|
|||||||
|
|
||||||
- **`cmd/`**: Contains main applications
|
- **`cmd/`**: Contains main applications
|
||||||
|
|
||||||
- `uc/`: CLI tool with subcommands for machine, service, volume management
|
- `uncloud/`: CLI tool with subcommands for machine, service, volume management
|
||||||
- `uncloudd/`: Daemon that runs on each machine
|
- `uncloudd/`: Daemon that runs on each machine
|
||||||
- `ucind/`: Development cluster management for testing
|
- `ucind/`: Development cluster management for testing
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ github.com/siderolabs/grpc-proxy // gRPC proxy for forwarding
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build binaries
|
# Build binaries
|
||||||
go build -o uc ./cmd/uc
|
go build -o uncloud ./cmd/uncloud
|
||||||
go build -o uncloudd ./cmd/uncloudd
|
go build -o uncloudd ./cmd/uncloudd
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ uc context use <name> # Switch context
|
|||||||
- **Package naming**: Use clear, descriptive names
|
- **Package naming**: Use clear, descriptive names
|
||||||
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
||||||
- **Logging**: Use structured logging with levels
|
- **Logging**: Use structured logging with levels
|
||||||
- **gRPC**: Services defined in `api/pb/`
|
- **gRPC**: Services defined in `internal/machine/api/pb/`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
@@ -196,6 +196,7 @@ uc context use <name> # Switch context
|
|||||||
- Unit tests alongside source files (`*_test.go`)
|
- Unit tests alongside source files (`*_test.go`)
|
||||||
- Integration tests in `test/e2e/`
|
- Integration tests in `test/e2e/`
|
||||||
- Test fixtures in `test/fixtures/`
|
- Test fixtures in `test/fixtures/`
|
||||||
|
- Use table driven tests whenever possible
|
||||||
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
@@ -238,7 +239,7 @@ uc context use <name> # Switch context
|
|||||||
|
|
||||||
### Important Files to Understand
|
### Important Files to Understand
|
||||||
|
|
||||||
- `cmd/uc/main.go`: CLI entry point and command structure
|
- `cmd/uncloud/main.go`: CLI entry point and command structure
|
||||||
- `internal/cli/cli.go`: CLI implementation and configuration
|
- `internal/cli/cli.go`: CLI implementation and configuration
|
||||||
- `internal/machine/machine.go`: Core machine management
|
- `internal/machine/machine.go`: Core machine management
|
||||||
- `pkg/api/`: Public API definitions
|
- `pkg/api/`: Public API definitions
|
||||||
|
|||||||
+5
-9
@@ -18,7 +18,6 @@ RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o uncloudd ./cmd/uncloudd
|
|||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION} AS corrosion-download
|
FROM alpine:${ALPINE_VERSION} AS corrosion-download
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG CORROSION_VERSION
|
|
||||||
|
|
||||||
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
||||||
"amd64") echo "x86_64" ;; \
|
"amd64") echo "x86_64" ;; \
|
||||||
@@ -26,21 +25,20 @@ RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
|||||||
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
|
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
|
||||||
esac) \
|
esac) \
|
||||||
&& wget -q -O /tmp/corrosion.tar.gz \
|
&& wget -q -O /tmp/corrosion.tar.gz \
|
||||||
"https://github.com/psviderski/corrosion/releases/download/v${CORROSION_VERSION}/corrosion-${CORROSION_ARCH}-unknown-linux-gnu.tar.gz" \
|
"https://github.com/psviderski/corrosion/releases/latest/download/corrosion-${CORROSION_ARCH}-unknown-linux-gnu.tar.gz" \
|
||||||
&& tar -xzf /tmp/corrosion.tar.gz -C /tmp \
|
&& tar -xzf /tmp/corrosion.tar.gz -C /tmp \
|
||||||
&& install /tmp/corrosion /usr/bin/corrosion \
|
&& install /tmp/corrosion /usr/local/bin/corrosion \
|
||||||
&& rm /tmp/corrosion.tar.gz /tmp/corrosion
|
&& rm /tmp/corrosion.tar.gz /tmp/corrosion
|
||||||
|
|
||||||
# Beware that more modern images like chainguard/wolfi-base build glibc with flags that require newer CPU features,
|
# Beware that more modern images like chainguard/wolfi-base build glibc with flags that require newer CPU features,
|
||||||
# e.g. x86-64-v2. So such image may fail with "Fatal glibc error: CPU does not support x86-64-v2" on older CPUs.
|
# e.g. x86-64-v2. So such image may fail with "Fatal glibc error: CPU does not support x86-64-v2" on older CPUs.
|
||||||
FROM gcr.io/distroless/cc-debian13:latest AS corrosion
|
FROM gcr.io/distroless/cc-debian12:latest AS corrosion
|
||||||
COPY --from=corrosion-download /usr/bin/corrosion /usr/bin/corrosion
|
COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion
|
||||||
CMD ["corrosion", "agent"]
|
CMD ["corrosion", "agent"]
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball
|
FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball
|
||||||
ARG CORROSION_VERSION
|
ARG CORROSION_IMAGE="ghcr.io/psviderski/corrosion:latest"
|
||||||
ARG CORROSION_IMAGE="ghcr.io/unlabs-dev/corrosion:${CORROSION_VERSION}"
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
@@ -49,8 +47,6 @@ RUN crane pull --platform ${TARGETOS}/${TARGETARCH} "${CORROSION_IMAGE}" /corros
|
|||||||
|
|
||||||
# Uncloud-in-Docker (ucind) image for running Uncloud test clusters using Docker.
|
# Uncloud-in-Docker (ucind) image for running Uncloud test clusters using Docker.
|
||||||
FROM docker:29.4.0-dind AS ucind
|
FROM docker:29.4.0-dind AS ucind
|
||||||
ARG CORROSION_VERSION
|
|
||||||
|
|
||||||
# Create system group and user 'uncloud'.
|
# Create system group and user 'uncloud'.
|
||||||
RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud
|
RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
|
|||||||
+3
-3
@@ -32,13 +32,13 @@ available outside the project, so they don't interfere with system packages.
|
|||||||
Build the CLI:
|
Build the CLI:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build -o uc ./cmd/uc
|
go build -o uc ./cmd/uncloud
|
||||||
```
|
```
|
||||||
|
|
||||||
Or build and run the CLI with a single command:
|
Or build and run the CLI with a single command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go run ./cmd/uc --help
|
go run ./cmd/uncloud --help
|
||||||
```
|
```
|
||||||
|
|
||||||
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
||||||
@@ -107,7 +107,7 @@ make lint-and-fix
|
|||||||
|
|
||||||
## Code generation
|
## Code generation
|
||||||
|
|
||||||
Update the generated Go code for the gRPC API after modifying files in [`api/pb`](./api/pb):
|
Update the generated Go code for the machine gRPC API after modifying `.proto` files:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mise run proto
|
mise run proto
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# TODO: Makefile is deprecated, add new targets as mise tasks in mise.toml instead.
|
# TODO: Makefile is deprecated, add new targets as mise tasks in mise.toml instead.
|
||||||
|
|
||||||
CORROSION_VERSION ?= 2026.6.15
|
CORROSION_IMAGE ?= ghcr.io/psviderski/corrosion:latest
|
||||||
CORROSION_IMAGE = ghcr.io/unlabs-dev/corrosion:$(CORROSION_VERSION)
|
|
||||||
UCIND_IMAGE ?= ghcr.io/psviderski/ucind:latest
|
UCIND_IMAGE ?= ghcr.io/psviderski/ucind:latest
|
||||||
|
|
||||||
.PHONY: ucind-cluster
|
.PHONY: ucind-cluster
|
||||||
@@ -10,12 +9,11 @@ ucind-cluster:
|
|||||||
|
|
||||||
.PHONY: corrosion-image
|
.PHONY: corrosion-image
|
||||||
corrosion-image:
|
corrosion-image:
|
||||||
docker build --build-arg CORROSION_VERSION=$(CORROSION_VERSION) -t "$(CORROSION_IMAGE)" --target corrosion .
|
docker build -t "$(CORROSION_IMAGE)" --target corrosion .
|
||||||
|
|
||||||
.PHONY: corrosion-multiarch-image-push
|
.PHONY: corrosion-multiarch-image-push
|
||||||
corrosion-multiarch-image-push:
|
corrosion-multiarch-image-push:
|
||||||
docker buildx build --build-arg CORROSION_VERSION=$(CORROSION_VERSION) --push --platform linux/amd64,linux/arm64 \
|
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(CORROSION_IMAGE)" --target corrosion .
|
||||||
-t "$(CORROSION_IMAGE)" --target corrosion .
|
|
||||||
|
|
||||||
.PHONY: ucind-multiarch-image-push
|
.PHONY: ucind-multiarch-image-push
|
||||||
ucind-multiarch-image-push:
|
ucind-multiarch-image-push:
|
||||||
@@ -72,4 +70,4 @@ _lint:
|
|||||||
|
|
||||||
.PHONY: cli-docs
|
.PHONY: cli-docs
|
||||||
cli-docs:
|
cli-docs:
|
||||||
go run ./cmd/uc docs
|
go run ./cmd/uncloud docs
|
||||||
@@ -44,8 +44,6 @@ complexity of Kubernetes.
|
|||||||
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
||||||
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||||
Encrypt.
|
Encrypt.
|
||||||
* **Cluster storage for TLS certificates**: Native cluster storage for Caddy shares certificates and ACME challenge
|
|
||||||
tokens across machines and coordinates issuance.
|
|
||||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
@@ -156,6 +154,7 @@ Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
|||||||
```bash
|
```bash
|
||||||
$ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
$ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
||||||
|
|
||||||
|
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
||||||
⏳ Running Uncloud install script...
|
⏳ Running Uncloud install script...
|
||||||
✓ Docker is already installed.
|
✓ Docker is already installed.
|
||||||
⏳ Installing Docker...
|
⏳ Installing Docker...
|
||||||
@@ -168,10 +167,11 @@ $ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
|||||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||||
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
|
||||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||||
|
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz
|
||||||
|
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
||||||
|
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
||||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||||
✓ Uncloud machine daemon started.
|
✓ Uncloud machine daemon started.
|
||||||
✓ Uncloud installed on the machine successfully! 🎉
|
✓ Uncloud installed on the machine successfully! 🎉
|
||||||
@@ -190,20 +190,21 @@ DNS records updated to use only the internet-reachable machines running caddy se
|
|||||||
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
|
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
|
||||||
```
|
```
|
||||||
|
|
||||||
1. The CLI SSHs into the machine and installs Docker and the `uncloudd` machine daemon, managed by systemd.
|
1. The CLI SSHs into the machine and installs Docker, the `uncloudd` machine daemon and
|
||||||
|
[corrosion](https://github.com/superfly/corrosion) service, managed by systemd.
|
||||||
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
||||||
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
||||||
through its gRPC API over SSH.
|
through its gRPC API over SSH.
|
||||||
3. Configures and starts [corrosion](https://github.com/superfly/corrosion), a CRDT-based distributed SQLite database to
|
3. Configures and starts `corrosion`, a CRDT-based distributed SQLite database to share cluster state between machines.
|
||||||
share cluster state between machines, as a Docker container managed by `uncloudd`.
|
|
||||||
4. Creates a Docker bridge network connected to the WireGuard interface.
|
4. Creates a Docker bridge network connected to the WireGuard interface.
|
||||||
5. This machine becomes an entry point for the newly created cluster. Its address is stored as a connection info in the
|
5. This machine becomes an entry point for the newly created cluster which is stored in the cluster config under
|
||||||
cluster config at `~/.config/uncloud/config.yaml` on your local machine.
|
`~/.config/uncloud` on your local machine.
|
||||||
|
|
||||||
**When you add another machine:**
|
**When you add another machine:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uc machine add --name hetzner-server root@5.223.45.199
|
$ uc machine add --name hetzner-server root@5.223.45.199
|
||||||
|
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
||||||
⏳ Running Uncloud install script...
|
⏳ Running Uncloud install script...
|
||||||
✓ Docker is already installed.
|
✓ Docker is already installed.
|
||||||
✓ Linux user and group 'uncloud' created.
|
✓ Linux user and group 'uncloud' created.
|
||||||
@@ -212,10 +213,11 @@ $ uc machine add --name hetzner-server root@5.223.45.199
|
|||||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||||
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
|
||||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||||
|
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-x86_64-unknown-linux-gnu.tar.gz
|
||||||
|
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
||||||
|
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
||||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||||
✓ Uncloud machine daemon started.
|
✓ Uncloud machine daemon started.
|
||||||
✓ Uncloud installed on the machine successfully! 🎉
|
✓ Uncloud installed on the machine successfully! 🎉
|
||||||
@@ -244,10 +246,9 @@ hetzner-server Up 10.210.1.1/24 5.223.45.199 5.223.45.199:51820, [2
|
|||||||
3. Registers the second machine in the cluster state and exchanges WireGuard keys with the first machine.
|
3. Registers the second machine in the cluster state and exchanges WireGuard keys with the first machine.
|
||||||
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
||||||
network to communicate directly across machines.
|
network to communicate directly across machines.
|
||||||
5. Configures and starts the `uncloud-corrosion` container on the second machine to sync the cluster state.
|
5. Configures and starts `corrosion` on the second machine to sync the cluster state.
|
||||||
6. The second machine is added as an alternative entry point in the cluster config.
|
6. The second machine is added as an alternative entry point in the cluster config.
|
||||||
7. If one of the machines goes offline, the other machine can still be used by `uc` to connect and run cluster
|
7. If one of the machines goes offline, the other machine can still serve cluster operations.
|
||||||
operations.
|
|
||||||
|
|
||||||
If one more machine is added, the process repeats with a new subnet. The new machine needs to establish a WireGuard
|
If one more machine is added, the process repeats with a new subnet. The new machine needs to establish a WireGuard
|
||||||
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
||||||
@@ -325,9 +326,9 @@ I'm grateful to the following projects that inspired Uncloud's design and implem
|
|||||||
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
|
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
|
||||||
user experience that feels like magic.
|
user experience that feels like magic.
|
||||||
* [Talos Linux](https://github.com/siderolabs/talos)
|
* [Talos Linux](https://github.com/siderolabs/talos)
|
||||||
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for inspiring Uncloud's API routing
|
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for the machine API design using
|
||||||
design with [grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for their elegant approach to secure
|
[grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for its elegant approach to secure WireGuard-based overlay
|
||||||
WireGuard-based overlay networking with zero configuration.
|
networking with zero configuration.
|
||||||
* [Docker Swarm Classic](https://github.com/docker-archive/classicswarm) and
|
* [Docker Swarm Classic](https://github.com/docker-archive/classicswarm) and
|
||||||
[Rancher 1.x](http://rancher-com-website-main-elb-elb-1798790864.us-west-2.elb.amazonaws.com/docs/rancher/v1.6/en/)
|
[Rancher 1.x](http://rancher-com-website-main-elb-elb-1798790864.us-west-2.elb.amazonaws.com/docs/rancher/v1.6/en/)
|
||||||
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the
|
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the
|
||||||
|
|||||||
@@ -1,848 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.34.2
|
|
||||||
// protoc v5.27.3
|
|
||||||
// source: api/pb/caddy_storage.proto
|
|
||||||
|
|
||||||
package pb
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type StoreCaddyStorageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
|
||||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StoreCaddyStorageRequest) Reset() {
|
|
||||||
*x = StoreCaddyStorageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StoreCaddyStorageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*StoreCaddyStorageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *StoreCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use StoreCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*StoreCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StoreCaddyStorageRequest) GetKey() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Key
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StoreCaddyStorageRequest) GetValue() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadCaddyStorageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageRequest) Reset() {
|
|
||||||
*x = LoadCaddyStorageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*LoadCaddyStorageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use LoadCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*LoadCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageRequest) GetKey() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Key
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadCaddyStorageResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
|
||||||
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageResponse) Reset() {
|
|
||||||
*x = LoadCaddyStorageResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*LoadCaddyStorageResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use LoadCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*LoadCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{2}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageResponse) GetValue() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LoadCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
|
||||||
if x != nil {
|
|
||||||
return x.UpdatedAt
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteCaddyStorageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DeleteCaddyStorageRequest) Reset() {
|
|
||||||
*x = DeleteCaddyStorageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DeleteCaddyStorageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*DeleteCaddyStorageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *DeleteCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use DeleteCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*DeleteCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{3}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DeleteCaddyStorageRequest) GetKey() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Key
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListCaddyStorageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
|
||||||
Recursive bool `protobuf:"varint,2,opt,name=recursive,proto3" json:"recursive,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageRequest) Reset() {
|
|
||||||
*x = ListCaddyStorageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ListCaddyStorageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ListCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ListCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{4}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageRequest) GetPrefix() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Prefix
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageRequest) GetRecursive() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.Recursive
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListCaddyStorageResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Keys []string `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageResponse) Reset() {
|
|
||||||
*x = ListCaddyStorageResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ListCaddyStorageResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ListCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ListCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCaddyStorageResponse) GetKeys() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Keys
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatCaddyStorageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageRequest) Reset() {
|
|
||||||
*x = StatCaddyStorageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*StatCaddyStorageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use StatCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*StatCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{6}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageRequest) GetKey() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Key
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatCaddyStorageResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
|
||||||
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
|
||||||
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
|
|
||||||
IsTerminal bool `protobuf:"varint,4,opt,name=is_terminal,json=isTerminal,proto3" json:"is_terminal,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) Reset() {
|
|
||||||
*x = StatCaddyStorageResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*StatCaddyStorageResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use StatCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*StatCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{7}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) GetKey() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Key
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
|
||||||
if x != nil {
|
|
||||||
return x.UpdatedAt
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) GetSize() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Size
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StatCaddyStorageResponse) GetIsTerminal() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.IsTerminal
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListCertificatesResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Certificates []*IssuedCertificate `protobuf:"bytes,1,rep,name=certificates,proto3" json:"certificates,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCertificatesResponse) Reset() {
|
|
||||||
*x = ListCertificatesResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCertificatesResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ListCertificatesResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *ListCertificatesResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ListCertificatesResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ListCertificatesResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{8}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ListCertificatesResponse) GetCertificates() []*IssuedCertificate {
|
|
||||||
if x != nil {
|
|
||||||
return x.Certificates
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type IssuedCertificate struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
|
||||||
San string `protobuf:"bytes,1,opt,name=san,proto3" json:"san,omitempty"`
|
|
||||||
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
|
||||||
Chain []byte `protobuf:"bytes,2,opt,name=chain,proto3" json:"chain,omitempty"`
|
|
||||||
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
|
||||||
// JSON-encoded, may be absent or null.
|
|
||||||
IssuerData []byte `protobuf:"bytes,3,opt,name=issuer_data,json=issuerData,proto3" json:"issuer_data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) Reset() {
|
|
||||||
*x = IssuedCertificate{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*IssuedCertificate) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use IssuedCertificate.ProtoReflect.Descriptor instead.
|
|
||||||
func (*IssuedCertificate) Descriptor() ([]byte, []int) {
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{9}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) GetSan() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.San
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) GetChain() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Chain
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *IssuedCertificate) GetIssuerData() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.IssuerData
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_api_pb_caddy_storage_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_api_pb_caddy_storage_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x1a, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 0x5f, 0x73,
|
|
||||||
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70,
|
|
||||||
0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
|
||||||
0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f,
|
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
|
|
||||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
|
||||||
0x42, 0x0a, 0x18, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
|
||||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
|
||||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
|
||||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
|
|
||||||
0x6c, 0x75, 0x65, 0x22, 0x2b, 0x0a, 0x17, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79,
|
|
||||||
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10,
|
|
||||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
|
||||||
0x22, 0x6b, 0x0a, 0x18, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
|
||||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
|
||||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
|
||||||
0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
|
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
|
||||||
0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x2d, 0x0a,
|
|
||||||
0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72,
|
|
||||||
0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
|
||||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4f, 0x0a, 0x17,
|
|
||||||
0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69,
|
|
||||||
0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12,
|
|
||||||
0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01,
|
|
||||||
0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x22, 0x2e, 0x0a,
|
|
||||||
0x18, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
|
||||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79,
|
|
||||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x2b, 0x0a,
|
|
||||||
0x17, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
|
||||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
|
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x9c, 0x01, 0x0a, 0x18, 0x53,
|
|
||||||
0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
|
|
||||||
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
|
||||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
|
|
||||||
0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01,
|
|
||||||
0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x74,
|
|
||||||
0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69,
|
|
||||||
0x73, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x18, 0x4c, 0x69, 0x73,
|
|
||||||
0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73,
|
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
|
||||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70,
|
|
||||||
0x69, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
|
||||||
0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
|
||||||
0x73, 0x22, 0x5c, 0x0a, 0x11, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69,
|
|
||||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x6e, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x61, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69,
|
|
||||||
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1f,
|
|
||||||
0x0a, 0x0b, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20,
|
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x32,
|
|
||||||
0xaa, 0x03, 0x0a, 0x0c, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
|
||||||
0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
|
||||||
0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
|
||||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
|
||||||
0x12, 0x43, 0x0a, 0x04, 0x4c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
|
|
||||||
0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x61,
|
|
||||||
0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73,
|
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
|
|
||||||
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64,
|
|
||||||
0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
|
||||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
|
||||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12,
|
|
||||||
0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53,
|
|
||||||
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
|
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
|
||||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04,
|
|
||||||
0x53, 0x74, 0x61, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43,
|
|
||||||
0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
|
||||||
0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64,
|
|
||||||
0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
|
||||||
0x65, 0x12, 0x49, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
|
||||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e,
|
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
|
||||||
0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24,
|
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64,
|
|
||||||
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70,
|
|
||||||
0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_api_pb_caddy_storage_proto_rawDescOnce sync.Once
|
|
||||||
file_api_pb_caddy_storage_proto_rawDescData = file_api_pb_caddy_storage_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_api_pb_caddy_storage_proto_rawDescGZIP() []byte {
|
|
||||||
file_api_pb_caddy_storage_proto_rawDescOnce.Do(func() {
|
|
||||||
file_api_pb_caddy_storage_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_storage_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_api_pb_caddy_storage_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_api_pb_caddy_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
|
||||||
var file_api_pb_caddy_storage_proto_goTypes = []any{
|
|
||||||
(*StoreCaddyStorageRequest)(nil), // 0: api.StoreCaddyStorageRequest
|
|
||||||
(*LoadCaddyStorageRequest)(nil), // 1: api.LoadCaddyStorageRequest
|
|
||||||
(*LoadCaddyStorageResponse)(nil), // 2: api.LoadCaddyStorageResponse
|
|
||||||
(*DeleteCaddyStorageRequest)(nil), // 3: api.DeleteCaddyStorageRequest
|
|
||||||
(*ListCaddyStorageRequest)(nil), // 4: api.ListCaddyStorageRequest
|
|
||||||
(*ListCaddyStorageResponse)(nil), // 5: api.ListCaddyStorageResponse
|
|
||||||
(*StatCaddyStorageRequest)(nil), // 6: api.StatCaddyStorageRequest
|
|
||||||
(*StatCaddyStorageResponse)(nil), // 7: api.StatCaddyStorageResponse
|
|
||||||
(*ListCertificatesResponse)(nil), // 8: api.ListCertificatesResponse
|
|
||||||
(*IssuedCertificate)(nil), // 9: api.IssuedCertificate
|
|
||||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
|
||||||
(*emptypb.Empty)(nil), // 11: google.protobuf.Empty
|
|
||||||
}
|
|
||||||
var file_api_pb_caddy_storage_proto_depIdxs = []int32{
|
|
||||||
10, // 0: api.LoadCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
|
||||||
10, // 1: api.StatCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
|
||||||
9, // 2: api.ListCertificatesResponse.certificates:type_name -> api.IssuedCertificate
|
|
||||||
0, // 3: api.CaddyStorage.Store:input_type -> api.StoreCaddyStorageRequest
|
|
||||||
1, // 4: api.CaddyStorage.Load:input_type -> api.LoadCaddyStorageRequest
|
|
||||||
3, // 5: api.CaddyStorage.Delete:input_type -> api.DeleteCaddyStorageRequest
|
|
||||||
4, // 6: api.CaddyStorage.List:input_type -> api.ListCaddyStorageRequest
|
|
||||||
6, // 7: api.CaddyStorage.Stat:input_type -> api.StatCaddyStorageRequest
|
|
||||||
11, // 8: api.CaddyStorage.ListCertificates:input_type -> google.protobuf.Empty
|
|
||||||
11, // 9: api.CaddyStorage.Store:output_type -> google.protobuf.Empty
|
|
||||||
2, // 10: api.CaddyStorage.Load:output_type -> api.LoadCaddyStorageResponse
|
|
||||||
11, // 11: api.CaddyStorage.Delete:output_type -> google.protobuf.Empty
|
|
||||||
5, // 12: api.CaddyStorage.List:output_type -> api.ListCaddyStorageResponse
|
|
||||||
7, // 13: api.CaddyStorage.Stat:output_type -> api.StatCaddyStorageResponse
|
|
||||||
8, // 14: api.CaddyStorage.ListCertificates:output_type -> api.ListCertificatesResponse
|
|
||||||
9, // [9:15] is the sub-list for method output_type
|
|
||||||
3, // [3:9] is the sub-list for method input_type
|
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
|
||||||
3, // [3:3] is the sub-list for extension extendee
|
|
||||||
0, // [0:3] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_api_pb_caddy_storage_proto_init() }
|
|
||||||
func file_api_pb_caddy_storage_proto_init() {
|
|
||||||
if File_api_pb_caddy_storage_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*StoreCaddyStorageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*LoadCaddyStorageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*LoadCaddyStorageResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*DeleteCaddyStorageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*ListCaddyStorageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*ListCaddyStorageResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*StatCaddyStorageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*StatCaddyStorageResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*ListCertificatesResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_api_pb_caddy_storage_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*IssuedCertificate); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_api_pb_caddy_storage_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 10,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 1,
|
|
||||||
},
|
|
||||||
GoTypes: file_api_pb_caddy_storage_proto_goTypes,
|
|
||||||
DependencyIndexes: file_api_pb_caddy_storage_proto_depIdxs,
|
|
||||||
MessageInfos: file_api_pb_caddy_storage_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_api_pb_caddy_storage_proto = out.File
|
|
||||||
file_api_pb_caddy_storage_proto_rawDesc = nil
|
|
||||||
file_api_pb_caddy_storage_proto_goTypes = nil
|
|
||||||
file_api_pb_caddy_storage_proto_depIdxs = nil
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package api;
|
|
||||||
|
|
||||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
|
||||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
|
||||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
|
||||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
|
||||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
|
||||||
service CaddyStorage {
|
|
||||||
rpc Store(StoreCaddyStorageRequest) returns (google.protobuf.Empty);
|
|
||||||
rpc Load(LoadCaddyStorageRequest) returns (LoadCaddyStorageResponse);
|
|
||||||
rpc Delete(DeleteCaddyStorageRequest) returns (google.protobuf.Empty);
|
|
||||||
rpc List(ListCaddyStorageRequest) returns (ListCaddyStorageResponse);
|
|
||||||
rpc Stat(StatCaddyStorageRequest) returns (StatCaddyStorageResponse);
|
|
||||||
|
|
||||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
|
||||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
|
||||||
rpc ListCertificates(google.protobuf.Empty) returns (ListCertificatesResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message StoreCaddyStorageRequest {
|
|
||||||
string key = 1;
|
|
||||||
bytes value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoadCaddyStorageRequest {
|
|
||||||
string key = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoadCaddyStorageResponse {
|
|
||||||
bytes value = 1;
|
|
||||||
google.protobuf.Timestamp updated_at = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteCaddyStorageRequest {
|
|
||||||
string key = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListCaddyStorageRequest {
|
|
||||||
string prefix = 1;
|
|
||||||
bool recursive = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListCaddyStorageResponse {
|
|
||||||
repeated string keys = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StatCaddyStorageRequest {
|
|
||||||
string key = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StatCaddyStorageResponse {
|
|
||||||
string key = 1;
|
|
||||||
google.protobuf.Timestamp updated_at = 2;
|
|
||||||
int64 size = 3;
|
|
||||||
bool is_terminal = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListCertificatesResponse {
|
|
||||||
repeated IssuedCertificate certificates = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IssuedCertificate {
|
|
||||||
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
|
||||||
string san = 1;
|
|
||||||
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
|
||||||
bytes chain = 2;
|
|
||||||
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
|
||||||
// JSON-encoded, may be absent or null.
|
|
||||||
bytes issuer_data = 3;
|
|
||||||
}
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
|
||||||
// - protoc v5.27.3
|
|
||||||
// source: api/pb/caddy_storage.proto
|
|
||||||
|
|
||||||
package pb
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
codes "google.golang.org/grpc/codes"
|
|
||||||
status "google.golang.org/grpc/status"
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.64.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion9
|
|
||||||
|
|
||||||
const (
|
|
||||||
CaddyStorage_Store_FullMethodName = "/api.CaddyStorage/Store"
|
|
||||||
CaddyStorage_Load_FullMethodName = "/api.CaddyStorage/Load"
|
|
||||||
CaddyStorage_Delete_FullMethodName = "/api.CaddyStorage/Delete"
|
|
||||||
CaddyStorage_List_FullMethodName = "/api.CaddyStorage/List"
|
|
||||||
CaddyStorage_Stat_FullMethodName = "/api.CaddyStorage/Stat"
|
|
||||||
CaddyStorage_ListCertificates_FullMethodName = "/api.CaddyStorage/ListCertificates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CaddyStorageClient is the client API for CaddyStorage service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
//
|
|
||||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
|
||||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
|
||||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
|
||||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
|
||||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
|
||||||
type CaddyStorageClient interface {
|
|
||||||
Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error)
|
|
||||||
Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error)
|
|
||||||
Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error)
|
|
||||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
|
||||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
|
||||||
ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type caddyStorageClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCaddyStorageClient(cc grpc.ClientConnInterface) CaddyStorageClient {
|
|
||||||
return &caddyStorageClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_Store_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(LoadCaddyStorageResponse)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_Load_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_Delete_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(ListCaddyStorageResponse)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_List_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(StatCaddyStorageResponse)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_Stat_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *caddyStorageClient) ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(ListCertificatesResponse)
|
|
||||||
err := c.cc.Invoke(ctx, CaddyStorage_ListCertificates_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaddyStorageServer is the server API for CaddyStorage service.
|
|
||||||
// All implementations must embed UnimplementedCaddyStorageServer
|
|
||||||
// for forward compatibility.
|
|
||||||
//
|
|
||||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
|
||||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
|
||||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
|
||||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
|
||||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
|
||||||
type CaddyStorageServer interface {
|
|
||||||
Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error)
|
|
||||||
Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error)
|
|
||||||
Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error)
|
|
||||||
List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error)
|
|
||||||
Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error)
|
|
||||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
|
||||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
|
||||||
ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error)
|
|
||||||
mustEmbedUnimplementedCaddyStorageServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnimplementedCaddyStorageServer must be embedded to have
|
|
||||||
// forward compatible implementations.
|
|
||||||
//
|
|
||||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
|
||||||
// pointer dereference when methods are called.
|
|
||||||
type UnimplementedCaddyStorageServer struct{}
|
|
||||||
|
|
||||||
func (UnimplementedCaddyStorageServer) Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Store not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Load not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Stat not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListCertificates not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedCaddyStorageServer) mustEmbedUnimplementedCaddyStorageServer() {}
|
|
||||||
func (UnimplementedCaddyStorageServer) testEmbeddedByValue() {}
|
|
||||||
|
|
||||||
// UnsafeCaddyStorageServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to CaddyStorageServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeCaddyStorageServer interface {
|
|
||||||
mustEmbedUnimplementedCaddyStorageServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterCaddyStorageServer(s grpc.ServiceRegistrar, srv CaddyStorageServer) {
|
|
||||||
// If the following call pancis, it indicates UnimplementedCaddyStorageServer was
|
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
|
||||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
|
||||||
t.testEmbeddedByValue()
|
|
||||||
}
|
|
||||||
s.RegisterService(&CaddyStorage_ServiceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_Store_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(StoreCaddyStorageRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).Store(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_Store_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).Store(ctx, req.(*StoreCaddyStorageRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_Load_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(LoadCaddyStorageRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).Load(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_Load_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).Load(ctx, req.(*LoadCaddyStorageRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DeleteCaddyStorageRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).Delete(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_Delete_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).Delete(ctx, req.(*DeleteCaddyStorageRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(ListCaddyStorageRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).List(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_List_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).List(ctx, req.(*ListCaddyStorageRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_Stat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(StatCaddyStorageRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).Stat(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_Stat_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).Stat(ctx, req.(*StatCaddyStorageRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CaddyStorage_ListCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(emptypb.Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(CaddyStorageServer).ListCertificates(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: CaddyStorage_ListCertificates_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(CaddyStorageServer).ListCertificates(ctx, req.(*emptypb.Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaddyStorage_ServiceDesc is the grpc.ServiceDesc for CaddyStorage service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var CaddyStorage_ServiceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "api.CaddyStorage",
|
|
||||||
HandlerType: (*CaddyStorageServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "Store",
|
|
||||||
Handler: _CaddyStorage_Store_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Load",
|
|
||||||
Handler: _CaddyStorage_Load_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Delete",
|
|
||||||
Handler: _CaddyStorage_Delete_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "List",
|
|
||||||
Handler: _CaddyStorage_List_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Stat",
|
|
||||||
Handler: _CaddyStorage_Stat_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "ListCertificates",
|
|
||||||
Handler: _CaddyStorage_ListCertificates_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "api/pb/caddy_storage.proto",
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,206 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package api;
|
|
||||||
|
|
||||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
|
||||||
|
|
||||||
import "google/protobuf/duration.proto";
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
import "api/pb/common.proto";
|
|
||||||
|
|
||||||
service Machine {
|
|
||||||
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
|
|
||||||
rpc CheckPrerequisites(google.protobuf.Empty) returns (CheckPrerequisitesResponse);
|
|
||||||
rpc InitCluster(InitClusterRequest) returns (InitClusterResponse);
|
|
||||||
rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty);
|
|
||||||
rpc Token(google.protobuf.Empty) returns (TokenResponse);
|
|
||||||
// Deprecated: use InspectMachine instead.
|
|
||||||
rpc Inspect(google.protobuf.Empty) returns (MachineInfo);
|
|
||||||
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
|
||||||
rpc InspectMachine(google.protobuf.Empty) returns (InspectMachineResponse);
|
|
||||||
// WaitForStoreVersion waits until the cluster store on this machine has reached each requested actor version, with
|
|
||||||
// no known missing or pending transactions through those versions from active members.
|
|
||||||
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
|
||||||
// have been superseded.
|
|
||||||
//
|
|
||||||
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
|
||||||
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
|
||||||
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
|
||||||
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
|
||||||
//
|
|
||||||
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
|
||||||
// record or condition should verify it after waiting.
|
|
||||||
//
|
|
||||||
// This RPC observes native replication without initiating synchronisation.
|
|
||||||
// Use the RPC deadline to bound the wait.
|
|
||||||
rpc WaitForStoreVersion(WaitForStoreVersionRequest) returns (google.protobuf.Empty);
|
|
||||||
// UpdateMachine updates the configuration of the machine.
|
|
||||||
rpc UpdateMachine(UpdateMachineRequest) returns (UpdateMachineResponse);
|
|
||||||
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
|
|
||||||
rpc InspectWireGuardNetwork(google.protobuf.Empty) returns (InspectWireGuardNetworkResponse);
|
|
||||||
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
|
||||||
rpc Reset(ResetRequest) returns (google.protobuf.Empty);
|
|
||||||
|
|
||||||
rpc InspectService(InspectServiceRequest) returns (InspectServiceResponse);
|
|
||||||
|
|
||||||
rpc MachineLogs(LogsRequest) returns (stream LogEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
message MachineInfo {
|
|
||||||
string id = 1;
|
|
||||||
string name = 2;
|
|
||||||
NetworkConfig network = 3;
|
|
||||||
IP public_ip = 4;
|
|
||||||
// Version of the machine daemon (uncloudd).
|
|
||||||
string daemon_version = 7;
|
|
||||||
// Version of the Docker engine running on the machine. Could be empty if unable to get.
|
|
||||||
string docker_version = 8;
|
|
||||||
|
|
||||||
// Operating system hostname.
|
|
||||||
string hostname = 5;
|
|
||||||
// CPU architecture.
|
|
||||||
string arch = 6;
|
|
||||||
// Human-readable operating system name and version, e.g. "Ubuntu 24.04.4 LTS". Empty if cannot be determined.
|
|
||||||
string os_pretty_name = 9;
|
|
||||||
// Kernel release version, e.g. "6.8.0-31-generic".
|
|
||||||
string kernel_version = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkConfig {
|
|
||||||
IPPrefix subnet = 1;
|
|
||||||
IP management_ip = 2;
|
|
||||||
repeated IPPort endpoints = 3;
|
|
||||||
bytes public_key = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateMachineRequest {
|
|
||||||
// Updated machine information. Only the set fields are applied.
|
|
||||||
optional string name = 1;
|
|
||||||
optional IP public_ip = 2;
|
|
||||||
repeated IPPort endpoints = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateMachineResponse {
|
|
||||||
MachineInfo machine = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CheckPrerequisitesResponse {
|
|
||||||
// Overall status of the checks.
|
|
||||||
bool satisfied = 1;
|
|
||||||
// Error message if not satisfied (empty if all checks pass).
|
|
||||||
string error = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitClusterRequest {
|
|
||||||
string machineName = 1;
|
|
||||||
IPPrefix network = 2;
|
|
||||||
|
|
||||||
oneof public_ip_config {
|
|
||||||
IP public_ip = 3;
|
|
||||||
bool public_ip_auto = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional WireGuard endpoints other machines will use to connect to this machine instead of auto-discovered ones.
|
|
||||||
repeated IPPort wireguard_endpoints = 5;
|
|
||||||
// WireGuard listen port for this machine. Uses the default port (51820) if 0 or not set.
|
|
||||||
int32 wireguard_port = 6;
|
|
||||||
// MTU of the WireGuard interface on this machine. The daemon auto-detects the optimal MTU if 0 or not set.
|
|
||||||
int32 wireguard_mtu = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitClusterResponse {
|
|
||||||
MachineInfo machine = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message JoinClusterRequest {
|
|
||||||
MachineInfo machine = 1;
|
|
||||||
repeated MachineInfo other_machines = 3;
|
|
||||||
// WireGuard listen port for this machine. Uses the default port (51820) if 0 or not set.
|
|
||||||
int32 wireguard_port = 5;
|
|
||||||
// MTU of the WireGuard interface on this machine. The daemon auto-detects the optimal MTU if 0 or not set.
|
|
||||||
int32 wireguard_mtu = 7;
|
|
||||||
// Cluster store version this machine must reach before participating.
|
|
||||||
// Per-actor vector (Corrosion actor UUID → minimum required db_version).
|
|
||||||
map<string, uint64> min_store_version = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectMachineResponse {
|
|
||||||
// Must contain only one repeated messages field to allow broadcasting InspectMachine requests to multiple machines.
|
|
||||||
repeated MachineDetails machines = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message MachineDetails {
|
|
||||||
Metadata metadata = 1;
|
|
||||||
MachineInfo machine = 2;
|
|
||||||
// Round-trip times to other machines in the cluster, keyed by peer machine ID.
|
|
||||||
map<string, RTTStats> rtts = 4;
|
|
||||||
// Store replication progress observed on this machine.
|
|
||||||
// Maps each Corrosion actor UUID to its highest processed database version. Corrosion also counts versions whose
|
|
||||||
// changes were superseded and skipped. Versions at or below a reported version may still be missing or pending
|
|
||||||
// locally.
|
|
||||||
//
|
|
||||||
// Pass this vector to WaitForStoreVersion on another machine to wait for replication through these versions.
|
|
||||||
// See that RPC's data-availability limitations. To combine observations from several machines, take the maximum
|
|
||||||
// for each actor.
|
|
||||||
// Capturing this vector does not wait for replication or prevent further writes.
|
|
||||||
map<string, uint64> store_version = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WaitForStoreVersionRequest {
|
|
||||||
// Minimum database version to reach for each Corrosion actor UUID.
|
|
||||||
// Obtain a vector from InspectMachine. To combine several responses, take the maximum version for each actor.
|
|
||||||
// Actors omitted from this vector and versions above its targets are not awaited. An empty vector requires
|
|
||||||
// no replication. Unknown actors with positive targets remain pending until replication reaches them or the RPC ends.
|
|
||||||
map<string, uint64> min_version = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenResponse {
|
|
||||||
string token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ResetRequest {
|
|
||||||
}
|
|
||||||
|
|
||||||
message Service {
|
|
||||||
string id = 1;
|
|
||||||
string name = 2;
|
|
||||||
string mode = 3;
|
|
||||||
|
|
||||||
message Container {
|
|
||||||
string machine_id = 1;
|
|
||||||
// JSON encoded Docker types.Container.
|
|
||||||
bytes container = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
repeated Container containers = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectServiceRequest {
|
|
||||||
string id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectServiceResponse {
|
|
||||||
Service service = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectWireGuardNetworkResponse {
|
|
||||||
string interface_name = 1;
|
|
||||||
bytes public_key = 2;
|
|
||||||
int32 listen_port = 3;
|
|
||||||
repeated WireGuardPeer peers = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WireGuardPeer {
|
|
||||||
bytes public_key = 1;
|
|
||||||
string endpoint = 2;
|
|
||||||
google.protobuf.Timestamp last_handshake_time = 3;
|
|
||||||
int64 receive_bytes = 4;
|
|
||||||
int64 transmit_bytes = 5;
|
|
||||||
repeated string allowed_ips = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RTTStats {
|
|
||||||
google.protobuf.Duration median = 1;
|
|
||||||
google.protobuf.Duration std_dev = 2;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
_ _ ___
|
|
||||||
| | | |/ __|
|
|
||||||
| |_| | (__
|
|
||||||
\__,_|\___|
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package cert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/caddyserver/certmagic"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type listOptions struct {
|
|
||||||
machine string
|
|
||||||
output string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewListCommand() *cobra.Command {
|
|
||||||
opts := listOptions{}
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List certificates in cluster storage for Caddy.",
|
|
||||||
Long: `List certificates stored in the Uncloud cluster storage for Caddy.
|
|
||||||
|
|
||||||
Caddy must use the Uncloud storage module (https://github.com/unlabs-dev/caddy-uncloud)
|
|
||||||
configured with 'storage uncloud' in the global options for its certificates to appear here.
|
|
||||||
|
|
||||||
This inventory does not check whether Caddy currently serves or trusts a certificate.`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
return list(cmd.Context(), uncli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
|
||||||
"Name or ID of the machine to read certificate storage from. (default is connected machine)")
|
|
||||||
cmd.Flags().StringVarP(&opts.output, "output", "o", "",
|
|
||||||
"Output format: 'json' or empty for a human-readable table.")
|
|
||||||
completion.MachinesFlag(cmd)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|
||||||
if opts.output != "" && opts.output != "json" {
|
|
||||||
return fmt.Errorf("unsupported output format '%s' (supported: json)", opts.output)
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
|
||||||
}
|
|
||||||
defer clusterClient.Close()
|
|
||||||
|
|
||||||
certs, listErr := clusterClient.Caddy.ListCertificates(ctx,
|
|
||||||
client.CaddyListCertificatesOptions{Machine: opts.machine})
|
|
||||||
if len(certs) > 0 || listErr == nil {
|
|
||||||
if err = printCertificates(os.Stdout, certs, opts.output); err != nil {
|
|
||||||
return fmt.Errorf("print Caddy certificates: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if listErr != nil {
|
|
||||||
if len(certs) > 0 {
|
|
||||||
return fmt.Errorf("certificate list is partial: %w", listErr)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("list Caddy certificates: %w", listErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printCertificates(out io.Writer, certs []api.IssuedCertificate, output string) error {
|
|
||||||
type certificate struct {
|
|
||||||
api.IssuedCertificate
|
|
||||||
Fingerprint string
|
|
||||||
}
|
|
||||||
|
|
||||||
items := make([]certificate, 0, len(certs))
|
|
||||||
for _, cert := range certs {
|
|
||||||
fingerprint := cert.Fingerprint()
|
|
||||||
items = append(items, certificate{
|
|
||||||
IssuedCertificate: cert,
|
|
||||||
Fingerprint: hex.EncodeToString(fingerprint[:]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
slices.SortFunc(items, func(a, b certificate) int {
|
|
||||||
if cmp := strings.Compare(a.SAN, b.SAN); cmp != 0 {
|
|
||||||
return cmp
|
|
||||||
}
|
|
||||||
if cmp := a.Chain[0].NotAfter.Compare(b.Chain[0].NotAfter); cmp != 0 {
|
|
||||||
return cmp
|
|
||||||
}
|
|
||||||
return strings.Compare(a.Fingerprint, b.Fingerprint)
|
|
||||||
})
|
|
||||||
|
|
||||||
if output == "json" {
|
|
||||||
encoder := json.NewEncoder(out)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
if err := encoder.Encode(items); err != nil {
|
|
||||||
return fmt.Errorf("marshal certificates: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(items) == 0 {
|
|
||||||
_, err := fmt.Fprintln(out, "No Caddy certificates found in cluster storage.")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tui.NewTable()
|
|
||||||
t.Headers("ID", "NAME", "ISSUER", "EXPIRES")
|
|
||||||
for _, item := range items {
|
|
||||||
t.Row(
|
|
||||||
item.Fingerprint[:12],
|
|
||||||
item.SAN,
|
|
||||||
formatIssuer(item.IssuerData),
|
|
||||||
formatExpiry(item.Chain[0].NotAfter),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_, err := lipgloss.Fprintln(out, t)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatIssuer(data api.CertificateIssuerData) string {
|
|
||||||
if data.ACME == nil {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch data.ACME.CA {
|
|
||||||
case certmagic.LetsEncryptProductionCA:
|
|
||||||
return "Let's Encrypt"
|
|
||||||
case certmagic.LetsEncryptStagingCA:
|
|
||||||
return "Let's Encrypt (staging)"
|
|
||||||
case certmagic.ZeroSSLProductionCA:
|
|
||||||
return "ZeroSSL"
|
|
||||||
case certmagic.GoogleTrustProductionCA:
|
|
||||||
return "Google Trust Services"
|
|
||||||
case certmagic.GoogleTrustStagingCA:
|
|
||||||
return "Google Trust Services (staging)"
|
|
||||||
default:
|
|
||||||
return data.ACME.CA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatExpiry(expires time.Time) string {
|
|
||||||
delta := expires.Sub(time.Now())
|
|
||||||
if delta <= 0 {
|
|
||||||
return fmt.Sprintf("%s (expired %s ago)", expires.UTC().Format(time.DateOnly),
|
|
||||||
strings.ToLower(units.HumanDuration(-delta)))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (%s)", expires.UTC().Format(time.DateOnly),
|
|
||||||
strings.ToLower(units.HumanDuration(delta)))
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package cert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrintCertificates(t *testing.T) {
|
|
||||||
now := time.Now().UTC().Truncate(time.Hour)
|
|
||||||
certs := []api.IssuedCertificate{
|
|
||||||
testIssuedCertificate("z.example.com", "z", now.Add(48*time.Hour), certmagic.LetsEncryptProductionCA),
|
|
||||||
testIssuedCertificate("a.example.com", "old", now.Add(-48*time.Hour), certmagic.LetsEncryptStagingCA),
|
|
||||||
testIssuedCertificate("a.example.com", "new", now.Add(72*time.Hour), "https://ca.example/directory"),
|
|
||||||
testIssuedCertificate("unknown.example.com", "unknown", now.Add(96*time.Hour), ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
var table bytes.Buffer
|
|
||||||
require.NoError(t, printCertificates(&table, certs, ""))
|
|
||||||
output := table.String()
|
|
||||||
assert.Contains(t, output, "ID")
|
|
||||||
assert.Contains(t, output, "NAME")
|
|
||||||
assert.Contains(t, output, "ISSUER")
|
|
||||||
assert.Contains(t, output, "EXPIRES")
|
|
||||||
assert.Contains(t, output, now.Add(-48*time.Hour).Format(time.DateOnly)+" (expired ")
|
|
||||||
assert.Contains(t, output, now.Add(48*time.Hour).Format(time.DateOnly)+" (")
|
|
||||||
assert.Contains(t, output, "Let's Encrypt (staging)")
|
|
||||||
assert.Contains(t, output, "https://ca.example/directory")
|
|
||||||
assert.Less(t, strings.Index(output, now.Add(-48*time.Hour).Format(time.DateOnly)),
|
|
||||||
strings.Index(output, now.Add(72*time.Hour).Format(time.DateOnly)))
|
|
||||||
assert.Less(t, strings.Index(output, "a.example.com"), strings.Index(output, "z.example.com"))
|
|
||||||
assert.Contains(t, output, testFingerprint("old")[:12])
|
|
||||||
|
|
||||||
var encoded bytes.Buffer
|
|
||||||
require.NoError(t, printCertificates(&encoded, certs, "json"))
|
|
||||||
var items []struct {
|
|
||||||
api.IssuedCertificate
|
|
||||||
Fingerprint string
|
|
||||||
}
|
|
||||||
require.NoError(t, json.Unmarshal(encoded.Bytes(), &items))
|
|
||||||
require.Len(t, items, 4)
|
|
||||||
assert.Equal(t, testFingerprint("old"), items[0].Fingerprint)
|
|
||||||
assert.Equal(t, "a.example.com", items[0].SAN)
|
|
||||||
require.Len(t, items[0].Chain, 1)
|
|
||||||
assert.Equal(t, []byte("old"), items[0].Chain[0].Raw)
|
|
||||||
assert.Equal(t, now.Add(-48*time.Hour), items[0].Chain[0].NotAfter)
|
|
||||||
require.NotNil(t, items[0].IssuerData.ACME)
|
|
||||||
assert.Equal(t, certmagic.LetsEncryptStagingCA, items[0].IssuerData.ACME.CA)
|
|
||||||
assert.Equal(t, testFingerprint("new"), items[1].Fingerprint)
|
|
||||||
require.NotNil(t, items[1].IssuerData.ACME)
|
|
||||||
assert.Equal(t, "https://ca.example/directory", items[1].IssuerData.ACME.CA)
|
|
||||||
assert.Nil(t, items[2].IssuerData.ACME)
|
|
||||||
assert.Equal(t, "z.example.com", items[3].SAN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrintCertificates_Empty(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
output string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{output: "", want: "No Caddy certificates found in cluster storage.\n"},
|
|
||||||
{output: "json", want: "[]\n"},
|
|
||||||
} {
|
|
||||||
var out bytes.Buffer
|
|
||||||
require.NoError(t, printCertificates(&out, nil, tt.output))
|
|
||||||
assert.Equal(t, tt.want, out.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatIssuer(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
ca string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{name: "production", ca: certmagic.LetsEncryptProductionCA, want: "Let's Encrypt"},
|
|
||||||
{name: "staging", ca: certmagic.LetsEncryptStagingCA, want: "Let's Encrypt (staging)"},
|
|
||||||
{name: "ZeroSSL", ca: certmagic.ZeroSSLProductionCA, want: "ZeroSSL"},
|
|
||||||
{name: "Google Trust Services", ca: certmagic.GoogleTrustProductionCA, want: "Google Trust Services"},
|
|
||||||
{name: "Google Trust Services staging", ca: certmagic.GoogleTrustStagingCA, want: "Google Trust Services (staging)"},
|
|
||||||
{name: "other", ca: "https://ca.example/directory", want: "https://ca.example/directory"},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.want, formatIssuer(api.CertificateIssuerData{
|
|
||||||
ACME: &api.ACMEIssuerData{CA: tt.ca},
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "unknown", formatIssuer(api.CertificateIssuerData{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatExpiry_UTC(t *testing.T) {
|
|
||||||
now := time.Now().UTC()
|
|
||||||
brisbane := time.FixedZone("AEST", 10*60*60)
|
|
||||||
expires := time.Date(now.Year(), now.Month(), now.Day()+3, 3, 0, 0, 0, brisbane)
|
|
||||||
assert.True(t, strings.HasPrefix(formatExpiry(expires), expires.UTC().Format(time.DateOnly)+" ("))
|
|
||||||
assert.NotEqual(t, expires.Format(time.DateOnly), expires.UTC().Format(time.DateOnly))
|
|
||||||
expired := now.Add(-2 * time.Hour)
|
|
||||||
assert.True(t, strings.HasPrefix(formatExpiry(expired), expired.UTC().Format(time.DateOnly)+" (expired "))
|
|
||||||
assert.Contains(t, formatExpiry(expired), " ago)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testIssuedCertificate(name, raw string, expires time.Time, ca string) api.IssuedCertificate {
|
|
||||||
cert := api.IssuedCertificate{
|
|
||||||
SAN: name,
|
|
||||||
Chain: []*x509.Certificate{{Raw: []byte(raw), NotAfter: expires}},
|
|
||||||
}
|
|
||||||
if ca != "" {
|
|
||||||
cert.IssuerData.ACME = &api.ACMEIssuerData{CA: ca}
|
|
||||||
}
|
|
||||||
return cert
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFingerprint(raw string) string {
|
|
||||||
sum := sha256.Sum256([]byte(raw))
|
|
||||||
return hex.EncodeToString(sum[:])
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package cert
|
|
||||||
|
|
||||||
import "github.com/spf13/cobra"
|
|
||||||
|
|
||||||
func NewRootCommand() *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "cert",
|
|
||||||
Short: "Inspect certificates in cluster storage for Caddy.",
|
|
||||||
}
|
|
||||||
cmd.AddCommand(NewListCommand())
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package caddy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/psviderski/uncloud/cmd/uc/service"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/logs"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewLogsCommand() *cobra.Command {
|
|
||||||
var options logs.Options
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "logs",
|
|
||||||
Aliases: []string{"log"},
|
|
||||||
Short: "View caddy logs.",
|
|
||||||
Long: `View caddy logs.
|
|
||||||
|
|
||||||
This calls "uc logs caddy", see "uc logs" for the documention.
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
args = append([]string{"caddy"}, args...)
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
return service.RunLogs(cmd.Context(), uncli, args, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommandArgsValidation(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
newCmd func() *cobra.Command
|
|
||||||
args []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "root accepts no args",
|
|
||||||
newCmd: NewRootCommand,
|
|
||||||
args: nil,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "root rejects extra args",
|
|
||||||
newCmd: NewRootCommand,
|
|
||||||
args: []string{"extra"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list accepts no args",
|
|
||||||
newCmd: NewListCommand,
|
|
||||||
args: nil,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list rejects extra args",
|
|
||||||
newCmd: NewListCommand,
|
|
||||||
args: []string{"extra"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "show accepts no args",
|
|
||||||
newCmd: NewShowCommand,
|
|
||||||
args: nil,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "show rejects extra args",
|
|
||||||
newCmd: NewShowCommand,
|
|
||||||
args: []string{"extra"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "connection accepts no args",
|
|
||||||
newCmd: NewConnectionCommand,
|
|
||||||
args: nil,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "connection rejects extra args",
|
|
||||||
newCmd: NewConnectionCommand,
|
|
||||||
args: []string{"extra"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "use accepts no args",
|
|
||||||
newCmd: NewUseCommand,
|
|
||||||
args: nil,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "use accepts one arg",
|
|
||||||
newCmd: NewUseCommand,
|
|
||||||
args: []string{"prod"},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "use rejects extra args",
|
|
||||||
newCmd: NewUseCommand,
|
|
||||||
args: []string{"prod", "extra"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cmd := tt.newCmd()
|
|
||||||
require.NotNil(t, cmd.Args)
|
|
||||||
|
|
||||||
err := cmd.Args(cmd, tt.args)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/grpcversion"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewSetCommand() *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "set DOMAIN_NAME",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Short: "Set or unset an externally managed cluster domain (EXPERIMENTAL).",
|
|
||||||
Long: "EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services.\n" +
|
|
||||||
"Configure wildcard DNS records for this domain with your DNS provider. " +
|
|
||||||
"Uncloud will not create, verify, update, or delete external DNS records.\n" +
|
|
||||||
"Pass an empty string to unset a manually set domain. " +
|
|
||||||
"Use 'uc dns release' to release a domain reserved in Uncloud DNS. " +
|
|
||||||
"Setting or unsetting the domain does not change existing service hostnames.",
|
|
||||||
Example: " uc dns set apps.example.com\n uc dns set \"\"",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
return set(cmd.Context(), uncli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func set(ctx context.Context, uncli *cli.CLI, name string) error {
|
|
||||||
if name != "" {
|
|
||||||
if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
|
|
||||||
return fmt.Errorf("invalid cluster domain %q: must be a valid domain name with at least two labels", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tui.PrintWarning("Setting an externally managed cluster domain is experimental.")
|
|
||||||
|
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
|
||||||
}
|
|
||||||
defer clusterClient.Close()
|
|
||||||
|
|
||||||
ctx = metadata.AppendToOutgoingContext(ctx, grpcversion.MetadataKeyMinServerVersion, "0.21.0")
|
|
||||||
_, err = clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name})
|
|
||||||
if err != nil {
|
|
||||||
if status.Convert(err).Code() == codes.AlreadyExists {
|
|
||||||
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' or " +
|
|
||||||
"release a reservation with 'uc dns release' first")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("set cluster domain: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
fmt.Println("Unset cluster domain. External DNS records have not been changed.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fmt.Printf("Set cluster domain: %s\n", name)
|
|
||||||
fmt.Println("DNS is managed externally. " +
|
|
||||||
"Configure wildcard DNS records pointing to machines running the Caddy service.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package machine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// runVersionFlagEnvTest builds the given command for each case, stubs out RunE so only flag
|
|
||||||
// parsing and PreRunE run (no SSH calls), and asserts the resolved --version flag value.
|
|
||||||
// Note: cannot use t.Parallel() because subtests use t.Setenv().
|
|
||||||
func runVersionFlagEnvTest(t *testing.T, newCommand func() *cobra.Command) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
env string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "env var sets version when flag not passed",
|
|
||||||
args: []string{"root@localhost"},
|
|
||||||
env: "v1.2.3",
|
|
||||||
want: "v1.2.3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "explicit flag wins over env var",
|
|
||||||
args: []string{"root@localhost", "--version", "v9.9.9"},
|
|
||||||
env: "v1.2.3",
|
|
||||||
want: "v9.9.9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "defaults to latest when neither flag nor env var set",
|
|
||||||
args: []string{"root@localhost"},
|
|
||||||
want: "latest",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if tt.env != "" {
|
|
||||||
t.Setenv("UNCLOUD_DAEMON_VERSION", tt.env)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := newCommand()
|
|
||||||
cmd.RunE = func(*cobra.Command, []string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cmd.SetArgs(tt.args)
|
|
||||||
require.NoError(t, cmd.Execute())
|
|
||||||
|
|
||||||
version, err := cmd.Flags().GetString("version")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, version)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInitCommandVersionFlagEnvVar verifies that the UNCLOUD_DAEMON_VERSION environment variable
|
|
||||||
// is bound to the --version flag of 'machine init', and that an explicit flag wins.
|
|
||||||
func TestInitCommandVersionFlagEnvVar(t *testing.T) {
|
|
||||||
runVersionFlagEnvTest(t, NewInitCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAddCommandVersionFlagEnvVar verifies that the UNCLOUD_DAEMON_VERSION environment variable
|
|
||||||
// is bound to the --version flag of 'machine add', and that an explicit flag wins.
|
|
||||||
func TestAddCommandVersionFlagEnvVar(t *testing.T) {
|
|
||||||
runVersionFlagEnvTest(t, NewAddCommand)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package machine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/logs"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRunLogsInvalidTimeFilters(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Invalid filters must fail before connecting to the cluster.
|
|
||||||
for _, flag := range []string{"since", "until"} {
|
|
||||||
opts := logs.Options{}
|
|
||||||
if flag == "since" {
|
|
||||||
opts.Since = "invalid"
|
|
||||||
} else {
|
|
||||||
opts.Until = "invalid"
|
|
||||||
}
|
|
||||||
err := runLogs(context.Background(), nil, nil, opts)
|
|
||||||
require.ErrorContains(t, err, "invalid --"+flag+" value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApplyGlobalFlagsFromCompletionArgs(t *testing.T) {
|
|
||||||
defaultConfigPath := "~/.config/uncloud/config.yaml"
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
wantConnect string
|
|
||||||
wantContext string
|
|
||||||
wantConfigPath string
|
|
||||||
// Flag names expected to be marked as changed on the target flag set.
|
|
||||||
wantChanged []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no flags",
|
|
||||||
args: []string{"__complete", "inspect", ""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "connect with space",
|
|
||||||
args: []string{"__complete", "--connect", "ssh://user@host", "inspect", ""},
|
|
||||||
wantConnect: "ssh://user@host",
|
|
||||||
wantChanged: []string{"connect"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "connect with equals",
|
|
||||||
args: []string{"__complete", "--connect=tcp://127.0.0.1:51000", "inspect", ""},
|
|
||||||
wantConnect: "tcp://127.0.0.1:51000",
|
|
||||||
wantChanged: []string{"connect"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "context shorthand",
|
|
||||||
args: []string{"__complete", "-c", "prod", "inspect", ""},
|
|
||||||
wantContext: "prod",
|
|
||||||
wantChanged: []string{"context"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "all flags",
|
|
||||||
args: []string{"__complete", "--connect", "user@host", "-c", "prod", "--uncloud-config", "/tmp/uncloud.yaml", "inspect", ""},
|
|
||||||
wantConnect: "user@host",
|
|
||||||
wantContext: "prod",
|
|
||||||
wantConfigPath: "/tmp/uncloud.yaml",
|
|
||||||
wantChanged: []string{"connect", "context", "uncloud-config"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unknown flags are ignored",
|
|
||||||
args: []string{"__complete", "--quiet", "-n", "5", "--connect", "user@host", "logs", ""},
|
|
||||||
wantConnect: "user@host",
|
|
||||||
wantChanged: []string{"connect"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flags after double dash are ignored",
|
|
||||||
args: []string{"__complete", "exec", "svc", "--", "sh", "--connect", "user@host"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flags before double dash are applied",
|
|
||||||
args: []string{"__complete", "--connect", "user@host", "exec", "svc", "--", "sh", "-c", "env"},
|
|
||||||
wantConnect: "user@host",
|
|
||||||
wantChanged: []string{"connect"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "partial flag name being completed is excluded",
|
|
||||||
args: []string{"__complete", "--connect", "user@host", "inspect", "--context"},
|
|
||||||
wantConnect: "user@host",
|
|
||||||
wantChanged: []string{"connect"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "partial flag value being completed is excluded",
|
|
||||||
args: []string{"__complete", "--uncloud-config", "/tmp/"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "partial connect value being completed is excluded",
|
|
||||||
args: []string{"__complete", "--connect", "tcp://127.0.0.1:5"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "completed flag value with partial command word",
|
|
||||||
args: []string{"__complete", "--uncloud-config", "/tmp/uncloud.yaml", "insp"},
|
|
||||||
wantConfigPath: "/tmp/uncloud.yaml",
|
|
||||||
wantChanged: []string{"uncloud-config"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// Mirror the global persistent flags defined on the root command.
|
|
||||||
var opts globalOptions
|
|
||||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
|
||||||
flags.StringVar(&opts.connect, "connect", "", "")
|
|
||||||
flags.StringVarP(&opts.context, "context", "c", "", "")
|
|
||||||
flags.StringVar(&opts.configPath, "uncloud-config", defaultConfigPath, "")
|
|
||||||
|
|
||||||
applyGlobalFlagsFromCompletionArgs(flags, tt.args)
|
|
||||||
|
|
||||||
assert.Equal(t, tt.wantConnect, opts.connect)
|
|
||||||
assert.Equal(t, tt.wantContext, opts.context)
|
|
||||||
wantConfigPath := tt.wantConfigPath
|
|
||||||
if wantConfigPath == "" {
|
|
||||||
wantConfigPath = defaultConfigPath
|
|
||||||
}
|
|
||||||
assert.Equal(t, wantConfigPath, opts.configPath)
|
|
||||||
|
|
||||||
for _, name := range []string{"connect", "context", "uncloud-config"} {
|
|
||||||
assert.Equal(t, slices.Contains(tt.wantChanged, name), flags.Changed(name),
|
|
||||||
"changed status of flag '%s'", name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-175
@@ -1,175 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/proxy"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type proxyOptions struct {
|
|
||||||
localPort int
|
|
||||||
remotePort int
|
|
||||||
service string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProxyCommand creates a new command to proxy a local port to a service's port in the cluster.
|
|
||||||
func NewProxyCommand() *cobra.Command {
|
|
||||||
opts := proxyOptions{}
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "proxy SERVICE [LOCAL_PORT:]REMOTE_PORT",
|
|
||||||
Args: cobra.ExactArgs(2),
|
|
||||||
Short: "Proxy a service port to a local port.",
|
|
||||||
Long: `Proxy a service port in the cluster to a local port on this machine.
|
|
||||||
|
|
||||||
If the service runs multiple containers, the command connects to the first running and healthy one.
|
|
||||||
If you don't provide a local port, the command picks a random one.
|
|
||||||
|
|
||||||
The connection stays open for as long as the command runs.`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
|
|
||||||
opts.service = args[0]
|
|
||||||
|
|
||||||
parts := strings.Split(args[1], ":")
|
|
||||||
switch len(parts) {
|
|
||||||
case 1:
|
|
||||||
remoteport, err := strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid remote port: '%s': %w", parts[0], err)
|
|
||||||
}
|
|
||||||
opts.remotePort = remoteport
|
|
||||||
case 2:
|
|
||||||
localport, err := strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid local port: '%s': %w", parts[0], err)
|
|
||||||
}
|
|
||||||
remoteport, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid remote port: '%s': %w", parts[1], err)
|
|
||||||
}
|
|
||||||
opts.localPort = localport
|
|
||||||
opts.remotePort = remoteport
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid port")
|
|
||||||
}
|
|
||||||
|
|
||||||
return runProxy(cmd.Context(), uncli, opts)
|
|
||||||
},
|
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
}
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
return completion.Services(cmd.Context(), uncli, args, toComplete)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runProxy(ctx context.Context, uncli *cli.CLI, opts proxyOptions) error {
|
|
||||||
if opts.localPort < 0 || opts.localPort > 65535 {
|
|
||||||
return fmt.Errorf("invalid local port %d: must be between 0 and 65535", opts.localPort)
|
|
||||||
}
|
|
||||||
if opts.remotePort < 1 || opts.remotePort > 65535 {
|
|
||||||
return fmt.Errorf("invalid remote port %d: must be between 1 and 65535", opts.remotePort)
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
|
||||||
}
|
|
||||||
defer clusterClient.Close()
|
|
||||||
|
|
||||||
svc, err := clusterClient.InspectService(ctx, opts.service)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
|
||||||
return fmt.Errorf("service '%s' not found in the cluster", opts.service)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("inspect service '%s': %w", opts.service, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick the first running and healthy container to proxy to.
|
|
||||||
var ctr *api.MachineServiceContainer
|
|
||||||
for i := range svc.Containers {
|
|
||||||
if svc.Containers[i].Container.Healthy() {
|
|
||||||
ctr = &svc.Containers[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ctr == nil {
|
|
||||||
return fmt.Errorf("no running healthy container found for service '%s'", opts.service)
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID := ctr.Container.ShortID()
|
|
||||||
ip := ctr.Container.UncloudNetworkIP()
|
|
||||||
if !ip.IsValid() {
|
|
||||||
return fmt.Errorf("container '%s' is not connected to the uncloud Docker network (could be host network)",
|
|
||||||
containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer, err := clusterClient.Dialer()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get proxy dialer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(opts.localPort)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listen on 127.0.0.1:%d: %w", opts.localPort, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no precheck if we can connect, as this always succeeds, only the proxy connects with the
|
|
||||||
// endpoint and shuffles the data, *it* will actually experience errors.
|
|
||||||
remoteAddr := net.JoinHostPort(ip.String(), strconv.Itoa(opts.remotePort))
|
|
||||||
|
|
||||||
p := &proxy.Proxy{
|
|
||||||
Listener: listener,
|
|
||||||
RemoteAddr: remoteAddr,
|
|
||||||
DialContext: dialer.DialContext,
|
|
||||||
OnError: func(err error) {
|
|
||||||
if proxy.IsConnectionClosedError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// A more actionable error instead of the cryptic [ssh -W] command error.
|
|
||||||
if strings.Contains(err.Error(), "Session open refused by peer") {
|
|
||||||
fmt.Printf("Could not connect to '%s': connection refused. "+
|
|
||||||
"Check that the service is running and listening on port %d inside the container.\n",
|
|
||||||
remoteAddr, opts.remotePort)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Failed to proxy a connection to '%s': %v\n", remoteAddr, err)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix the local address with the scheme for common HTTP ports so it becomes control-clickable in most
|
|
||||||
// terminals. We assume plain HTTP since TLS is typically terminated by Caddy in front of the service.
|
|
||||||
fmt.Printf("%s%s → %s (%s%s%s)\n", schemeForPort(opts.remotePort), p.Listener.Addr().String(),
|
|
||||||
remoteAddr, opts.service, tui.Faint.Render("/"), containerID)
|
|
||||||
|
|
||||||
if err = p.Run(ctx); err != nil {
|
|
||||||
return fmt.Errorf("run proxy to '%s': %w", remoteAddr, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// schemeForPort returns the "http://" URL scheme prefix for the ports most likely to serve plain HTTP,
|
|
||||||
// or an empty string otherwise.
|
|
||||||
func schemeForPort(port int) string {
|
|
||||||
switch port {
|
|
||||||
case 80, 3000, 8000, 8080, 8081, 8888, 9090:
|
|
||||||
return "http://"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockDockerClient struct {
|
|
||||||
pb.DockerClient
|
|
||||||
listResp *pb.ListServiceContainersResponse
|
|
||||||
listErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockDockerClient) ListServiceContainers(ctx context.Context, in *pb.ListServiceContainersRequest, opts ...grpc.CallOption) (*pb.ListServiceContainersResponse, error) {
|
|
||||||
return m.listResp, m.listErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers(t *testing.T) {
|
|
||||||
containerData1 := map[string]interface{}{
|
|
||||||
"Id": "container1",
|
|
||||||
"Name": "container-1",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "image-1",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON1, _ := json.Marshal(containerData1)
|
|
||||||
|
|
||||||
containerData2 := map[string]any{
|
|
||||||
"Id": "container2",
|
|
||||||
"Name": "container-2",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "image-2",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON2, _ := json.Marshal(containerData2)
|
|
||||||
|
|
||||||
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
|
||||||
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{MachineAddr: "10.0.0.1", MachineName: "machine-1"},
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON1,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{MachineAddr: "10.0.0.2", MachineName: "machine-2"},
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON2,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{GRPCClient: mockDocker},
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := collectContainers(context.Background(), cli)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, containers, 2)
|
|
||||||
for _, c := range containers {
|
|
||||||
if c.id == "container1" {
|
|
||||||
assert.Equal(t, "machine-1", c.machineName)
|
|
||||||
} else if c.id == "container2" {
|
|
||||||
assert.Equal(t, "machine-2", c.machineName)
|
|
||||||
} else {
|
|
||||||
t.Errorf("unexpected container id: %s", c.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/logs"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRunLogsInvalidTimeFilters(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Invalid filters must fail before loading Compose files or connecting to the cluster.
|
|
||||||
for _, flag := range []string{"since", "until"} {
|
|
||||||
opts := logs.Options{}
|
|
||||||
if flag == "since" {
|
|
||||||
opts.Since = "invalid"
|
|
||||||
} else {
|
|
||||||
opts.Until = "invalid"
|
|
||||||
}
|
|
||||||
err := RunLogs(context.Background(), nil, nil, opts)
|
|
||||||
require.ErrorContains(t, err, "invalid --"+flag+" value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/version"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed art.txt
|
|
||||||
var asciiArt string
|
|
||||||
|
|
||||||
// NewVersionCommand creates a new command to print the version and build information for the binary.
|
|
||||||
func NewVersionCommand() *cobra.Command {
|
|
||||||
var output string
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "version",
|
|
||||||
Short: "Show version and build information.",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
info := version.GetInfo()
|
|
||||||
w := cmd.OutOrStdout()
|
|
||||||
|
|
||||||
switch output {
|
|
||||||
case "":
|
|
||||||
fmt.Fprint(w, humanVersion(info))
|
|
||||||
case "json":
|
|
||||||
s, err := info.JSONString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, s)
|
|
||||||
default:
|
|
||||||
s, err := templateVersion(output, info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&output, "output", "o", "",
|
|
||||||
"Output format: 'json' or a Go template (e.g. '{{.Version}}').\n"+
|
|
||||||
"Run with '-o json' to discover the field names available to the template.\n"+
|
|
||||||
"(default is human-readable)")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanVersion(info version.Info) string {
|
|
||||||
var b strings.Builder
|
|
||||||
b.WriteString(asciiArt)
|
|
||||||
b.WriteString("\n")
|
|
||||||
b.WriteString(fmt.Sprintf("uc: Uncloud CLI tool for deploying apps and managing resources (%s)",
|
|
||||||
tui.URLStyle.Render(version.WebsiteURL)))
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
b.WriteString(info.String())
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateVersion renders the version info using the provided Go template.
|
|
||||||
func templateVersion(tmpl string, info version.Info) (string, error) {
|
|
||||||
t, err := template.New("version").Parse(tmpl)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("parse template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err = t.Execute(&buf, info); err != nil {
|
|
||||||
return "", fmt.Errorf("execute template: %w", err)
|
|
||||||
}
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
||||||
@@ -10,10 +10,9 @@ import (
|
|||||||
|
|
||||||
func NewRemoveCommand() *cobra.Command {
|
func NewRemoveCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm [NAME]",
|
Use: "rm [NAME]",
|
||||||
Short: "Remove a cluster.",
|
Short: "Remove a cluster.",
|
||||||
Aliases: []string{"remove", "delete"},
|
Args: cobra.MaximumNArgs(1),
|
||||||
Args: cobra.MaximumNArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
|
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -8,8 +8,6 @@ import (
|
|||||||
"github.com/alecthomas/chroma/v2/quick"
|
"github.com/alecthomas/chroma/v2/quick"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +46,15 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
|||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
config, err := clusterClient.Caddy.Config(ctx, client.CaddyConfigOptions{Machine: opts.machine})
|
if opts.machine != "" {
|
||||||
|
// If a specific machine is requested, use it to get the Caddy configuration.
|
||||||
|
ctx, _, err = clusterClient.ProxyMachinesContext(ctx, []string{opts.machine})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := clusterClient.Caddy.GetConfig(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get Caddy config: %w", err)
|
return fmt.Errorf("get Caddy config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -63,10 +69,5 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.LastReconciliationError != "" {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("last Caddy config load failed: %s\nShowing the last saved Caddyfile.",
|
|
||||||
config.LastReconciliationError))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -110,9 +110,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
placement := api.Placement{
|
placement := api.Placement{
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
||||||
}
|
}
|
||||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{
|
d, err := clusterClient.NewCaddyDeployment(opts.image, caddyfile, placement)
|
||||||
Image: opts.image, Config: caddyfile, Placement: placement,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -204,10 +202,6 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("get cluster domain: %w", err)
|
return fmt.Errorf("get cluster domain: %w", err)
|
||||||
}
|
}
|
||||||
if !domain.Reserved {
|
|
||||||
fmt.Printf("Skipping DNS records update as cluster domain '%s' is managed externally.\n", domain.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
||||||
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||||
@@ -222,7 +216,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||||
"machines running caddy containers.\n", domain.Name)
|
"machines running caddy containers.\n", domain)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Possible solutions:")
|
fmt.Println("Possible solutions:")
|
||||||
fmt.Println("- Ensure your machines have public IP addresses")
|
fmt.Println("- Ensure your machines have public IP addresses")
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/psviderski/uncloud/cmd/uc/caddy/cert"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,10 +10,8 @@ func NewRootCommand() *cobra.Command {
|
|||||||
Short: "Manage Caddy reverse proxy service.",
|
Short: "Manage Caddy reverse proxy service.",
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
cert.NewRootCommand(),
|
|
||||||
NewConfigCommand(),
|
NewConfigCommand(),
|
||||||
NewDeployCommand(),
|
NewDeployCommand(),
|
||||||
NewLogsCommand(),
|
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,6 @@ func NewConnectionCommand() *cobra.Command {
|
|||||||
Use: "connection",
|
Use: "connection",
|
||||||
Aliases: []string{"conn"},
|
Aliases: []string{"conn"},
|
||||||
Short: "Choose a new default connection for the current context.",
|
Short: "Choose a new default connection for the current context.",
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return selectConnection(uncli)
|
return selectConnection(uncli)
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -16,7 +15,6 @@ func NewListCommand() *cobra.Command {
|
|||||||
Use: "ls",
|
Use: "ls",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Short: "List available cluster contexts.",
|
Short: "List available cluster contexts.",
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return list(uncli)
|
return list(uncli)
|
||||||
@@ -51,6 +49,6 @@ func list(uncli *cli.CLI) error {
|
|||||||
t.Row(name, current, fmt.Sprintf("%d", connCount))
|
t.Row(name, current, fmt.Sprintf("%d", connCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ func NewRootCommand() *cobra.Command {
|
|||||||
Use: "ctx",
|
Use: "ctx",
|
||||||
Aliases: []string{"context"},
|
Aliases: []string{"context"},
|
||||||
Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.",
|
Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.",
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return selectContext(uncli)
|
return selectContext(uncli)
|
||||||
@@ -11,7 +11,6 @@ func NewShowCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "show",
|
Use: "show",
|
||||||
Short: "Show current cluster context.",
|
Short: "Show current cluster context.",
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return show(uncli)
|
return show(uncli)
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ func NewUseCommand() *cobra.Command {
|
|||||||
Short: "Switch to a different cluster context.",
|
Short: "Switch to a different cluster context.",
|
||||||
Long: "Switch to a different cluster context. If no context is provided, " +
|
Long: "Switch to a different cluster context. If no context is provided, " +
|
||||||
"a list of available contexts will be displayed for selection.",
|
"a list of available contexts will be displayed for selection.",
|
||||||
Args: cobra.MaximumNArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
@@ -51,10 +49,6 @@ func selectContext(uncli *cli.CLI) error {
|
|||||||
if len(uncli.Config.Contexts) == 0 {
|
if len(uncli.Config.Contexts) == 0 {
|
||||||
return fmt.Errorf("no contexts found in Uncloud config (%s)", uncli.Config.Path())
|
return fmt.Errorf("no contexts found in Uncloud config (%s)", uncli.Config.Path())
|
||||||
}
|
}
|
||||||
if !tui.IsTerminalAvailable() {
|
|
||||||
return fmt.Errorf("cannot select a context interactively without a terminal. " +
|
|
||||||
"Pass the context name explicitly: uc ctx use CONTEXT")
|
|
||||||
}
|
|
||||||
|
|
||||||
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
||||||
|
|
||||||
@@ -163,14 +163,6 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve 'secret://name' references to actual secret values before creating a deployment.
|
|
||||||
if compose.HasCommandSecretRefs(project) {
|
|
||||||
fmt.Fprintln(os.Stderr, "Resolving secrets...")
|
|
||||||
}
|
|
||||||
if err = compose.ResolveSecrets(ctx, project); err != nil {
|
|
||||||
return fmt.Errorf("resolve secrets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
strategy := &deploy.RollingStrategy{
|
strategy := &deploy.RollingStrategy{
|
||||||
ForceRecreate: opts.recreate,
|
ForceRecreate: opts.recreate,
|
||||||
SkipHealthMonitor: opts.skipHealth,
|
SkipHealthMonitor: opts.skipHealth,
|
||||||
@@ -211,7 +203,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
|
|
||||||
// Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes.
|
// Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes.
|
||||||
if !opts.yes {
|
if !opts.yes {
|
||||||
if !tui.IsTerminalAvailable() {
|
if !tui.IsStdinTerminal() {
|
||||||
return errors.New("cannot ask to confirm deployment plan in non-interactive mode, " +
|
return errors.New("cannot ask to confirm deployment plan in non-interactive mode, " +
|
||||||
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
||||||
}
|
}
|
||||||
@@ -305,7 +297,7 @@ const defaultFailedContainerLogsTail = 10
|
|||||||
// UNCLOUD_FAILED_CONTAINER_LOGS_TAIL environment variable override when set and valid.
|
// UNCLOUD_FAILED_CONTAINER_LOGS_TAIL environment variable override when set and valid.
|
||||||
func failedContainerLogsTail() int {
|
func failedContainerLogsTail() int {
|
||||||
if v := os.Getenv("UNCLOUD_FAILED_CONTAINER_LOGS_TAIL"); v != "" {
|
if v := os.Getenv("UNCLOUD_FAILED_CONTAINER_LOGS_TAIL"); v != "" {
|
||||||
if tail, err := logs.ParseTail(v); err == nil && (tail == -1 || tail > 0) {
|
if tail, err := logs.Tail(v); err == nil && (tail == -1 || tail > 0) {
|
||||||
return tail
|
return tail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,6 @@ func release(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
if status.Convert(err).Code() == codes.NotFound {
|
if status.Convert(err).Code() == codes.NotFound {
|
||||||
return errors.New("no domain reserved")
|
return errors.New("no domain reserved")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("release cluster domain: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Released cluster domain: %s\n", domain.Name)
|
fmt.Printf("Released cluster domain: %s\n", domain.Name)
|
||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -49,8 +49,7 @@ func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error {
|
|||||||
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
|
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if status.Convert(err).Code() == codes.AlreadyExists {
|
if status.Convert(err).Code() == codes.AlreadyExists {
|
||||||
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' " +
|
return errors.New("domain already reserved")
|
||||||
"or release a reservation with 'uc dns release' first")
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -7,19 +7,16 @@ import (
|
|||||||
func NewRootCommand() *cobra.Command {
|
func NewRootCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "dns",
|
Use: "dns",
|
||||||
Short: "Manage the cluster domain.",
|
Short: "Manage cluster domain in Uncloud DNS.",
|
||||||
Long: "Manage the cluster domain.\n" +
|
Long: "Manage cluster domain in Uncloud DNS.\n" +
|
||||||
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " +
|
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " +
|
||||||
"cluster. When reserved, Caddy service deployments will automatically update DNS records to route " +
|
"cluster. When reserved, Caddy service deployments will automatically update DNS records to route " +
|
||||||
"traffic to the services in the cluster.\n\n" +
|
"traffic to the services in the cluster.",
|
||||||
"EXPERIMENTAL: Use 'uc dns set' to configure an externally managed domain instead. " +
|
|
||||||
"Uncloud does not manage DNS records for manually set domains.",
|
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewReleaseCommand(),
|
NewReleaseCommand(),
|
||||||
NewReserveCommand(),
|
NewReserveCommand(),
|
||||||
NewShowCommand(),
|
NewShowCommand(),
|
||||||
NewSetCommand(),
|
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -33,11 +33,11 @@ func show(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
domain, err := clusterClient.GetDomain(ctx)
|
domain, err := clusterClient.GetDomain(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
return errors.New("no cluster domain configured")
|
return errors.New("no domain reserved")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(domain.Name)
|
fmt.Println(domain)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -88,6 +88,19 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
// Get all machines to create ID to name mapping.
|
||||||
|
allMachines, err := clusterClient.ListMachines(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list machines: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
machineIDToName := make(map[string]string)
|
||||||
|
for _, machineMember := range allMachines {
|
||||||
|
if machineMember.Machine != nil && machineMember.Machine.Id != "" && machineMember.Machine.Name != "" {
|
||||||
|
machineIDToName[machineMember.Machine.Id] = machineMember.Machine.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
||||||
|
|
||||||
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
|
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
|
||||||
@@ -102,14 +115,11 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
var rows []imageRow
|
var rows []imageRow
|
||||||
|
|
||||||
for _, machineImages := range clusterImages {
|
for _, machineImages := range clusterImages {
|
||||||
if err := machineImages.Error(); err != nil {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("failed to list images on machine '%s': %s",
|
|
||||||
machineImages.Metadata.MachineName, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get machine name for better readability.
|
// Get machine name for better readability.
|
||||||
machineName := machineImages.Metadata.MachineName
|
machineName := machineImages.Metadata.Machine
|
||||||
|
if m := allMachines.FindByNameOrID(machineName); m != nil {
|
||||||
|
machineName = m.Machine.Name
|
||||||
|
}
|
||||||
|
|
||||||
store := "docker"
|
store := "docker"
|
||||||
if machineImages.ContainerdStore {
|
if machineImages.ContainerdStore {
|
||||||
@@ -179,7 +189,7 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Print the images in a table format.
|
// Print the images in a table format.
|
||||||
lipgloss.Println(formatImageTable(rows))
|
fmt.Println(formatImageTable(rows))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/cmd/uc/image"
|
"github.com/psviderski/uncloud/cmd/uncloud/image"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -8,9 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/huh/v2/spinner"
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
@@ -28,8 +29,6 @@ type addOptions struct {
|
|||||||
sshKey string
|
sshKey string
|
||||||
version string
|
version string
|
||||||
wgEndpoints []string
|
wgEndpoints []string
|
||||||
wgPort int
|
|
||||||
wgMTU int
|
|
||||||
yes bool
|
yes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,19 +39,13 @@ func NewAddCommand() *cobra.Command {
|
|||||||
Short: "Add a remote machine to a cluster.",
|
Short: "Add a remote machine to a cluster.",
|
||||||
Long: `Add a new machine to an existing Uncloud cluster.
|
Long: `Add a new machine to an existing Uncloud cluster.
|
||||||
|
|
||||||
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
|
||||||
is specified, which assumes they are already installed and running.
|
|
||||||
|
|
||||||
Connection methods:
|
Connection methods:
|
||||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
|
||||||
cli.BindEnvToFlag(cmd, "version", "UNCLOUD_DAEMON_VERSION")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
// Determine connection mode and strip scheme.
|
// Determine connection mode and strip scheme.
|
||||||
@@ -77,15 +70,14 @@ Connection methods:
|
|||||||
return add(cmd.Context(), uncli, remoteMachine, opts)
|
return add(cmd.Context(), uncli, remoteMachine, opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(&opts.name, "name", "n", "",
|
cmd.Flags().StringVarP(&opts.name, "name", "n", "", "Assign a name to the machine.")
|
||||||
"Assign a name to the machine. (default is the machine's hostname)")
|
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noCaddy, "no-caddy", false,
|
&opts.noCaddy, "no-caddy", false,
|
||||||
"Don't deploy Caddy reverse proxy service to the machine.",
|
"Don't deploy Caddy reverse proxy service to the machine.",
|
||||||
)
|
)
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noInstall, "no-install", false,
|
&opts.noInstall, "no-install", false,
|
||||||
"Skip installation of Docker and the Uncloud daemon on the machine. "+
|
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
|
||||||
"Assumes they're already installed and running.",
|
"Assumes they're already installed and running.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
@@ -100,25 +92,17 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
"Version of the Uncloud daemon to install on the machine.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
&opts.wgEndpoints, "wg-endpoint", nil,
|
||||||
"WireGuard endpoint address that other machines in the cluster should use to establish "+
|
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"WireGuard connections\n"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\n"+
|
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", network.WireGuardPort)+
|
||||||
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
||||||
"Defaults to the auto-detected public and routable machine IPs.",
|
"Defaults to the auto-detected public and routable machine IPs.",
|
||||||
)
|
)
|
||||||
cmd.Flags().IntVar(
|
|
||||||
&opts.wgMTU, "wg-mtu", 0,
|
|
||||||
"MTU of the WireGuard network interface on the machine. (default auto-detects the optimal value)",
|
|
||||||
)
|
|
||||||
cmd.Flags().IntVar(
|
|
||||||
&opts.wgPort, "wg-port", network.DefaultWireGuardPort,
|
|
||||||
"UDP port WireGuard listens on for incoming connections from other machines.",
|
|
||||||
)
|
|
||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
||||||
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
||||||
@@ -141,26 +125,17 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.wgPort < 1 || opts.wgPort > 65535 {
|
|
||||||
return fmt.Errorf("invalid WireGuard port %d: must be between 1 and 65535", opts.wgPort)
|
|
||||||
}
|
|
||||||
if opts.wgMTU != 0 && (opts.wgMTU < network.MinWireGuardMTU || opts.wgMTU > 65535) {
|
|
||||||
return fmt.Errorf("invalid WireGuard MTU %d: must be 0 (auto-detect) or between %d and 65535",
|
|
||||||
opts.wgMTU, network.MinWireGuardMTU)
|
|
||||||
}
|
|
||||||
addOpts := cli.AddMachineOptions{
|
addOpts := cli.AddMachineOptions{
|
||||||
MachineName: opts.name,
|
MachineName: opts.name,
|
||||||
PublicIP: publicIP,
|
PublicIP: publicIP,
|
||||||
RemoteMachine: remoteMachine,
|
RemoteMachine: remoteMachine,
|
||||||
SkipInstall: opts.noInstall,
|
SkipInstall: opts.noInstall,
|
||||||
Version: opts.version,
|
Version: opts.version,
|
||||||
WireguardMTU: opts.wgMTU,
|
|
||||||
WireguardPort: opts.wgPort,
|
|
||||||
AutoConfirm: opts.yes,
|
AutoConfirm: opts.yes,
|
||||||
}
|
}
|
||||||
if len(opts.wgEndpoints) > 0 {
|
if len(opts.wgEndpoints) > 0 {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
||||||
}
|
}
|
||||||
@@ -179,9 +154,19 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
|
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
|
||||||
err = tui.RunSpinner(ctx, "Waiting for the machine to join the cluster...", func(ctx context.Context) error {
|
err = spinner.New().
|
||||||
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
|
Title(" Waiting for the machine to join the cluster...").
|
||||||
})
|
Type(spinner.MiniDot).
|
||||||
|
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
||||||
|
return &spinner.Styles{
|
||||||
|
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
||||||
|
Title: lipgloss.NewStyle(),
|
||||||
|
}
|
||||||
|
})).
|
||||||
|
ActionWithErr(func(ctx context.Context) error {
|
||||||
|
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
|
||||||
|
}).
|
||||||
|
Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wait for machine to join the cluster: %w", err)
|
return fmt.Errorf("wait for machine to join the cluster: %w", err)
|
||||||
}
|
}
|
||||||
@@ -192,7 +177,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
||||||
// use the deployed image version.
|
// use the deployed image version.
|
||||||
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
||||||
// issues accessing the API of existing machines in the cluster.
|
// issues accessing the Machine API of existing machines in the cluster.
|
||||||
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
|
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
|
||||||
caddyImage := ""
|
caddyImage := ""
|
||||||
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
||||||
@@ -219,7 +204,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Preparing Caddy deployment...")
|
fmt.Println("Preparing Caddy deployment...")
|
||||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: caddyImage})
|
d, err := clusterClient.NewCaddyDeployment(caddyImage, "", api.Placement{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -7,16 +7,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/huh/v2/spinner"
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/dns"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,8 +33,6 @@ type initOptions struct {
|
|||||||
sshKey string
|
sshKey string
|
||||||
version string
|
version string
|
||||||
wgEndpoints []string
|
wgEndpoints []string
|
||||||
wgPort int
|
|
||||||
wgMTU int
|
|
||||||
yes bool
|
yes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +44,6 @@ func NewInitCommand() *cobra.Command {
|
|||||||
Long: `Initialise a new cluster by setting up a remote machine as the first member.
|
Long: `Initialise a new cluster by setting up a remote machine as the first member.
|
||||||
This command creates a new context in your Uncloud config to manage the cluster.
|
This command creates a new context in your Uncloud config to manage the cluster.
|
||||||
|
|
||||||
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
|
||||||
is specified, which assumes they are already installed and running.
|
|
||||||
|
|
||||||
Connection methods:
|
Connection methods:
|
||||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
@@ -65,12 +61,9 @@ Connection methods:
|
|||||||
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
|
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
|
||||||
// TODO: support initialising a cluster on the local machine.
|
// TODO: support initialising a cluster on the local machine.
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
|
||||||
cli.BindEnvToFlag(cmd, "version", "UNCLOUD_DAEMON_VERSION")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
var remoteMachine *cli.RemoteMachine
|
var remoteMachine *cli.RemoteMachine
|
||||||
@@ -107,7 +100,7 @@ Connection methods:
|
|||||||
"API endpoint for the Uncloud DNS service.")
|
"API endpoint for the Uncloud DNS service.")
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.name, "name", "n", "",
|
&opts.name, "name", "n", "",
|
||||||
"Assign a name to the machine. (default is the machine's hostname)",
|
"Assign a name to the machine.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.network, "network", cluster.DefaultNetwork.String(),
|
&opts.network, "network", cluster.DefaultNetwork.String(),
|
||||||
@@ -123,7 +116,7 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noInstall, "no-install", false,
|
&opts.noInstall, "no-install", false,
|
||||||
"Skip installation of Docker and the Uncloud daemon on the machine. "+
|
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
|
||||||
"Assumes they're already installed and running.",
|
"Assumes they're already installed and running.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
@@ -138,25 +131,17 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
"Version of the Uncloud daemon to install on the machine.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
&opts.wgEndpoints, "wg-endpoint", nil,
|
||||||
"WireGuard endpoint address that other machines in the cluster should use to establish "+
|
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"WireGuard connections\n"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\n"+
|
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", network.WireGuardPort)+
|
||||||
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
||||||
"Defaults to the auto-detected public and routable machine IPs.",
|
"Defaults to the auto-detected public and routable machine IPs.",
|
||||||
)
|
)
|
||||||
cmd.Flags().IntVar(
|
|
||||||
&opts.wgMTU, "wg-mtu", 0,
|
|
||||||
"MTU of the WireGuard network interface on the machine. (default auto-detects the optimal value)",
|
|
||||||
)
|
|
||||||
cmd.Flags().IntVar(
|
|
||||||
&opts.wgPort, "wg-port", network.DefaultWireGuardPort,
|
|
||||||
"UDP port WireGuard listens on for incoming connections from other machines.",
|
|
||||||
)
|
|
||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
||||||
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
||||||
@@ -193,13 +178,6 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
if opts.wgPort < 1 || opts.wgPort > 65535 {
|
|
||||||
return fmt.Errorf("invalid WireGuard port %d: must be between 1 and 65535", opts.wgPort)
|
|
||||||
}
|
|
||||||
if opts.wgMTU != 0 && (opts.wgMTU < network.MinWireGuardMTU || opts.wgMTU > 65535) {
|
|
||||||
return fmt.Errorf("invalid WireGuard MTU %d: must be 0 (auto-detect) or between %d and 65535",
|
|
||||||
opts.wgMTU, network.MinWireGuardMTU)
|
|
||||||
}
|
|
||||||
initOpts := cli.InitClusterOptions{
|
initOpts := cli.InitClusterOptions{
|
||||||
Context: opts.context,
|
Context: opts.context,
|
||||||
MachineName: opts.name,
|
MachineName: opts.name,
|
||||||
@@ -208,31 +186,39 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
RemoteMachine: remoteMachine,
|
RemoteMachine: remoteMachine,
|
||||||
SkipInstall: opts.noInstall,
|
SkipInstall: opts.noInstall,
|
||||||
Version: opts.version,
|
Version: opts.version,
|
||||||
WireguardMTU: opts.wgMTU,
|
|
||||||
WireguardPort: opts.wgPort,
|
|
||||||
AutoConfirm: opts.yes,
|
AutoConfirm: opts.yes,
|
||||||
}
|
}
|
||||||
if len(opts.wgEndpoints) > 0 {
|
if len(opts.wgEndpoints) > 0 {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
||||||
}
|
}
|
||||||
initOpts.WireguardEndpoints = endpoints
|
initOpts.WireguardEndpoints = endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterClient, err := uncli.InitCluster(ctx, initOpts)
|
client, err := uncli.InitCluster(ctx, initOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Since the cluster API needs a few moments to become ready after cluster initialisation,
|
// Since the cluster API needs a few moments to become ready after cluster initialisation,
|
||||||
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
|
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
|
||||||
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
|
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
|
||||||
err = tui.RunSpinner(ctx, "Waiting for the cluster to be ready...", func(ctx context.Context) error {
|
err = spinner.New().
|
||||||
return clusterClient.WaitClusterReady(ctx, 1*time.Minute)
|
Title(" Waiting for the cluster to be ready...").
|
||||||
})
|
Type(spinner.MiniDot).
|
||||||
|
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
||||||
|
return &spinner.Styles{
|
||||||
|
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
||||||
|
Title: lipgloss.NewStyle(),
|
||||||
|
}
|
||||||
|
})).
|
||||||
|
ActionWithErr(func(ctx context.Context) error {
|
||||||
|
return client.WaitClusterReady(ctx, 1*time.Minute)
|
||||||
|
}).
|
||||||
|
Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wait for cluster to be ready: %w", err)
|
return fmt.Errorf("wait for cluster to be ready: %w", err)
|
||||||
}
|
}
|
||||||
@@ -245,7 +231,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
if !opts.noDNS {
|
if !opts.noDNS {
|
||||||
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
domain, err := client.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reserve cluster domain in Uncloud DNS: %w", err)
|
return fmt.Errorf("reserve cluster domain in Uncloud DNS: %w", err)
|
||||||
}
|
}
|
||||||
@@ -253,7 +239,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !opts.noCaddy {
|
if !opts.noCaddy {
|
||||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
|
d, err := client.NewCaddyDeployment("", "", api.Placement{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -269,7 +255,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return caddy.UpdateDomainRecords(ctx, clusterClient, uncli.ProgressOut())
|
return caddy.UpdateDomainRecords(ctx, client, uncli.ProgressOut())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -3,13 +3,10 @@ package machine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/logs"
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
|
"github.com/psviderski/uncloud/internal/journal"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -21,14 +18,14 @@ func NewLogsCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "logs [SERVICE...]",
|
Use: "logs [SERVICE...]",
|
||||||
Aliases: []string{"log"},
|
Aliases: []string{"log"},
|
||||||
Short: "View system service logs.",
|
Short: "View systemd service logs.",
|
||||||
Long: `View logs from the specified system service(s) across all machines in the cluster.
|
Long: `View logs from the specified systemd service(s) across all machines in the cluster.
|
||||||
Use -m to restrict to specific machines.
|
Use -m to restrict to specific machines.
|
||||||
|
|
||||||
Supported services:
|
Supported services:
|
||||||
corrosion the Corrosion distributed state store
|
uncloud the Uncloud daemon
|
||||||
docker the Docker daemon
|
docker the Docker daemon
|
||||||
uncloud the Uncloud daemon
|
uncloud-corrosion the Corrosion distributed state store
|
||||||
|
|
||||||
If no services are specified, streams logs from the uncloud service.`,
|
If no services are specified, streams logs from the uncloud service.`,
|
||||||
Example: ` # View recent logs for the uncloud service.
|
Example: ` # View recent logs for the uncloud service.
|
||||||
@@ -39,7 +36,7 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
uc machine logs -f uncloud
|
uc machine logs -f uncloud
|
||||||
|
|
||||||
# View logs from multiple services.
|
# View logs from multiple services.
|
||||||
uc machine logs uncloud docker corrosion
|
uc machine logs uncloud docker uncloud-corrosion
|
||||||
|
|
||||||
# Show last 20 lines per machine (default is 100).
|
# Show last 20 lines per machine (default is 100).
|
||||||
uc machine logs -n 20 docker
|
uc machine logs -n 20 docker
|
||||||
@@ -51,7 +48,7 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
uc machine logs --since 3h --until 1h30m docker
|
uc machine logs --since 3h --until 1h30m docker
|
||||||
|
|
||||||
# View logs only from specific machines.
|
# View logs only from specific machines.
|
||||||
uc machine logs -m machine1,machine2 uncloud corrosion`,
|
uc machine logs -m machine1,machine2 uncloud uncloud-corrosion`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return runLogs(cmd.Context(), uncli, args, options)
|
return runLogs(cmd.Context(), uncli, args, options)
|
||||||
@@ -59,31 +56,25 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
completion.MachinesFlag(cmd)
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.Options) error {
|
func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Options) error {
|
||||||
since, until, err := logs.TimeRange(opts.Since, opts.Until, time.Now())
|
if len(units) == 0 {
|
||||||
if err != nil {
|
units = []string{journal.UnitUncloud}
|
||||||
return err
|
|
||||||
}
|
|
||||||
tail, err := opts.TailLines()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(services) == 0 {
|
for _, unit := range units {
|
||||||
services = []string{api.SystemServiceUncloud}
|
if !journal.ValidUnit(unit) {
|
||||||
}
|
return fmt.Errorf("invalid systemd service '%s'", unit)
|
||||||
for _, service := range services {
|
|
||||||
if !slices.Contains(api.SystemServices, service) {
|
|
||||||
return fmt.Errorf("invalid system service '%s'; valid services: %s",
|
|
||||||
service, strings.Join(api.SystemServices, ", "))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tail, err := logs.Tail(opts.Tail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
c, err := uncli.ConnectCluster(ctx)
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
@@ -93,8 +84,8 @@ func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.O
|
|||||||
logsOpts := api.ServiceLogsOptions{
|
logsOpts := api.ServiceLogsOptions{
|
||||||
Follow: opts.Follow,
|
Follow: opts.Follow,
|
||||||
Tail: tail,
|
Tail: tail,
|
||||||
Since: since,
|
Since: opts.Since,
|
||||||
Until: until,
|
Until: opts.Until,
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,27 +101,27 @@ func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.O
|
|||||||
machineNames = append(machineNames, m.Machine.Name)
|
machineNames = append(machineNames, m.Machine.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect one log stream per service. MachineLogs merges across machines internally.
|
// Collect one log stream per unit. MachineLogs merges across machines internally.
|
||||||
serviceStreams := make([]<-chan api.ServiceLogEntry, 0, len(services))
|
unitStreams := make([]<-chan api.ServiceLogEntry, 0, len(units))
|
||||||
for _, service := range services {
|
for _, unit := range units {
|
||||||
ch, err := c.MachineLogs(ctx, service, logsOpts)
|
ch, err := c.MachineLogs(ctx, unit, logsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stream logs for system service '%s': %w", service, err)
|
return fmt.Errorf("stream logs for systemd service '%s': %w", unit, err)
|
||||||
}
|
}
|
||||||
serviceStreams = append(serviceStreams, ch)
|
unitStreams = append(unitStreams, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream <-chan api.ServiceLogEntry
|
var stream <-chan api.ServiceLogEntry
|
||||||
if len(serviceStreams) == 1 {
|
if len(unitStreams) == 1 {
|
||||||
stream = serviceStreams[0]
|
stream = unitStreams[0]
|
||||||
} else {
|
} else {
|
||||||
// Each MachineLogs stream already runs its own inner merger with stall detection,
|
// Each MachineLogs stream already runs its own inner merger with stall detection,
|
||||||
// so the outer merger across services skips it to avoid duplicate warnings.
|
// so the outer merger across units skips it to avoid duplicate warnings.
|
||||||
merger := client.NewLogMerger(serviceStreams, client.LogMergerOptions{})
|
merger := client.NewLogMerger(unitStreams, client.LogMergerOptions{})
|
||||||
stream = merger.Stream()
|
stream = merger.Stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
formatter := logs.NewFormatter(machineNames, services, opts.UTC)
|
formatter := logs.NewFormatter(machineNames, units, opts.UTC)
|
||||||
|
|
||||||
// Print merged logs.
|
// Print merged logs.
|
||||||
for entry := range stream {
|
for entry := range stream {
|
||||||
@@ -2,12 +2,10 @@ package machine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
@@ -15,29 +13,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewListCommand() *cobra.Command {
|
func NewListCommand() *cobra.Command {
|
||||||
var output string
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Short: "List machines in a cluster.",
|
Short: "List machines in a cluster.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return list(cmd.Context(), uncli, output)
|
return list(cmd.Context(), uncli)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&output, "output", "o", "",
|
|
||||||
"Output format: 'json' or empty for a human-readable table.")
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(ctx context.Context, uncli *cli.CLI, output string) error {
|
func list(ctx context.Context, uncli *cli.CLI) error {
|
||||||
if output != "" && output != "json" {
|
|
||||||
return fmt.Errorf("unsupported output format '%s' (supported: json)", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := uncli.ConnectCluster(ctx)
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
@@ -49,19 +37,9 @@ func list(ctx context.Context, uncli *cli.CLI, output string) error {
|
|||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if output == "json" {
|
|
||||||
data, err := json.MarshalIndent(machines.ToNative(), "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("marshal machines: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the list of machines in a table format.
|
// Print the list of machines in a table format.
|
||||||
t := tui.NewTable()
|
t := tui.NewTable()
|
||||||
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS",
|
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS", "MACHINE ID")
|
||||||
"OS", "KERNEL", "ARCH", "DOCKER", "VERSION")
|
|
||||||
|
|
||||||
for _, member := range machines {
|
for _, member := range machines {
|
||||||
m := member.Machine
|
m := member.Machine
|
||||||
@@ -80,46 +58,17 @@ func list(ctx context.Context, uncli *cli.CLI, output string) error {
|
|||||||
endpoints[i] = addrPort.String()
|
endpoints[i] = addrPort.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
arch := "-"
|
|
||||||
if m.Arch != "" {
|
|
||||||
arch = m.Arch
|
|
||||||
}
|
|
||||||
|
|
||||||
osName := "-"
|
|
||||||
if m.OsPrettyName != "" {
|
|
||||||
osName = m.OsPrettyName
|
|
||||||
}
|
|
||||||
|
|
||||||
kernel := "-"
|
|
||||||
if m.KernelVersion != "" {
|
|
||||||
kernel = m.KernelVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
daemonVersion := "-"
|
|
||||||
if m.DaemonVersion != "" {
|
|
||||||
daemonVersion = m.DaemonVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerVersion := "-"
|
|
||||||
if m.DockerVersion != "" {
|
|
||||||
dockerVersion = m.DockerVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Row(
|
t.Row(
|
||||||
m.Name,
|
m.Name,
|
||||||
capitalise(member.State.String()),
|
capitalise(member.State.String()),
|
||||||
subnet.String(),
|
subnet.String(),
|
||||||
publicIP,
|
publicIP,
|
||||||
strings.Join(endpoints, tui.Faint.Render(", ")),
|
strings.Join(endpoints, tui.Faint.Render(", ")),
|
||||||
osName,
|
member.Machine.Id,
|
||||||
kernel,
|
|
||||||
arch,
|
|
||||||
dockerVersion,
|
|
||||||
daemonVersion,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -13,10 +13,10 @@ import (
|
|||||||
"charm.land/lipgloss/v2/tree"
|
"charm.land/lipgloss/v2/tree"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -63,16 +63,15 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Verify the machine exists in the cluster.
|
// Verify the machine exists and list all service containers on it including stopped ones.
|
||||||
member, err := client.InspectMachine(ctx, nameOrID)
|
mctx, machines, err := client.ProxyMachinesContext(ctx, []string{nameOrID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("inspect machine '%s': %w", nameOrID, err)
|
return err
|
||||||
}
|
}
|
||||||
m := member.Machine
|
if len(machines) == 0 {
|
||||||
|
return fmt.Errorf("machine '%s' not found in the cluster", nameOrID)
|
||||||
// Create a proxy context for the machine being removed.
|
}
|
||||||
// This is used for calls that need to run directly on that machine.
|
m := machines[0].Machine
|
||||||
rmCtx := client.ProxySingleMachineContext(ctx, m.Id)
|
|
||||||
|
|
||||||
// Verify if the machine being removed is the proxy machine we're connected to.
|
// Verify if the machine being removed is the proxy machine we're connected to.
|
||||||
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
|
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
|
||||||
@@ -104,7 +103,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
if reset {
|
if reset {
|
||||||
// Check if the machine is up and has service containers.
|
// Check if the machine is up and has service containers.
|
||||||
listOpts := container.ListOptions{All: true}
|
listOpts := container.ListOptions{All: true}
|
||||||
machineContainers, err := client.Docker.ListServiceContainers(rmCtx, "", listOpts)
|
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reachable = true
|
reachable = true
|
||||||
containers = machineContainers[0].Containers
|
containers = machineContainers[0].Containers
|
||||||
@@ -114,7 +113,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
plural = "s"
|
plural = "s"
|
||||||
}
|
}
|
||||||
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
||||||
lipgloss.Println(formatContainerTree(containers))
|
fmt.Println(formatContainerTree(containers))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
|
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
|
||||||
"and reset it to the uninitialised state.")
|
"and reset it to the uninitialised state.")
|
||||||
@@ -136,7 +135,8 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
return fmt.Errorf("confirm removal: %w", err)
|
return fmt.Errorf("confirm removal: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
return cli.Cancelled("Cancelled. Machine was not removed.")
|
fmt.Println("Cancelled. Machine was not removed.")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,22 +150,20 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate reset before removing the machine from the cluster to stop it from updating the cluster store.
|
|
||||||
// This is still optimistic as Reset only triggers the reset process that runs asynchronously.
|
|
||||||
if reset && reachable {
|
|
||||||
_, err = client.MachineClient.Reset(rmCtx, &pb.ResetRequest{})
|
|
||||||
if err != nil {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("Failed to reset machine: %v\n", err))
|
|
||||||
} else {
|
|
||||||
fmt.Println("Machine reset initiated and will complete in the background.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
|
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
|
||||||
return fmt.Errorf("remove machine from cluster: %w", err)
|
return fmt.Errorf("remove machine from cluster: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
||||||
|
|
||||||
|
if reset && reachable {
|
||||||
|
_, err = client.MachineClient.Reset(mctx, &pb.ResetRequest{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("WARNING: Failed to reset machine: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Machine reset initiated and will complete in the background.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the connection to the machine from the uncloud config if it exists.
|
// Remove the connection to the machine from the uncloud config if it exists.
|
||||||
if uncli.Config != nil {
|
if uncli.Config != nil {
|
||||||
contextName := uncli.ContextOverrideOrCurrent()
|
contextName := uncli.ContextOverrideOrCurrent()
|
||||||
File renamed without changes.
@@ -6,7 +6,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -39,7 +38,10 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Setup context to proxy request to all machines.
|
// Setup context to proxy request to all machines.
|
||||||
ctx = client.ProxyMachinesContext(ctx, nil)
|
ctx, _, err = client.ProxyMachinesContext(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setup proxy context: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,15 +51,6 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
// Map machine IDs to names for display from the response.
|
// Map machine IDs to names for display from the response.
|
||||||
machineNames := make(map[string]string)
|
machineNames := make(map[string]string)
|
||||||
for _, m := range resp.Machines {
|
for _, m := range resp.Machines {
|
||||||
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
|
|
||||||
if m.Metadata == nil {
|
|
||||||
tui.PrintWarning("metadata is missing in response from unknown server")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if m.Metadata.Error != "" {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("failed to inspect machine '%s': %s", m.Metadata.MachineName, m.Metadata.Error))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if m.Machine == nil {
|
if m.Machine == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -108,7 +101,7 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
t.Row(r.machine, r.peer, tui.FormatRTT(r.median), formatRTTStdDev(r.stdDev))
|
t.Row(r.machine, r.peer, tui.FormatRTT(r.median), formatRTTStdDev(r.stdDev))
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -70,8 +70,7 @@ At least one flag must be specified to perform an update.`,
|
|||||||
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"WireGuard connections\n"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n",
|
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", network.WireGuardPort)+
|
||||||
network.DefaultWireGuardPort)+
|
|
||||||
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.",
|
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,13 +89,16 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Resolve the machine to capture its current configuration for the before/after report and to validate existence.
|
// First, resolve the machine to get its ID
|
||||||
machine, err := client.InspectMachine(ctx, machineNameOrID)
|
machine, err := client.InspectMachine(ctx, machineNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("find machine: %w", err)
|
return fmt.Errorf("find machine: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &pb.UpdateMachineRequest{}
|
// Build the update request
|
||||||
|
req := &pb.UpdateMachineRequest{
|
||||||
|
MachineId: machine.Machine.Id,
|
||||||
|
}
|
||||||
|
|
||||||
if opts.name != "" {
|
if opts.name != "" {
|
||||||
req.Name = &opts.name
|
req.Name = &opts.name
|
||||||
@@ -119,7 +121,7 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
|
|||||||
// Parse and set endpoints if the flag was explicitly provided.
|
// Parse and set endpoints if the flag was explicitly provided.
|
||||||
if cmd.Flags().Changed("wg-endpoint") {
|
if cmd.Flags().Changed("wg-endpoint") {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded, network.DefaultWireGuardPort)
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,8 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
|
|||||||
req.Endpoints = endpoints
|
req.Endpoints = endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedMachine, err := client.UpdateMachine(ctx, machine.Machine.Id, req)
|
// Perform the update operation
|
||||||
|
updatedMachine, err := client.UpdateMachine(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update machine: %w", err)
|
return fmt.Errorf("update machine: %w", err)
|
||||||
}
|
}
|
||||||
@@ -8,23 +8,22 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
"charm.land/lipgloss/v2"
|
||||||
cmdcontext "github.com/psviderski/uncloud/cmd/uc/context"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/dns"
|
cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/image"
|
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
||||||
cmdmachine "github.com/psviderski/uncloud/cmd/uc/machine"
|
"github.com/psviderski/uncloud/cmd/uncloud/image"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/service"
|
cmdmachine "github.com/psviderski/uncloud/cmd/uncloud/machine"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/volume"
|
"github.com/psviderski/uncloud/cmd/uncloud/service"
|
||||||
"github.com/psviderski/uncloud/cmd/uc/wg"
|
"github.com/psviderski/uncloud/cmd/uncloud/volume"
|
||||||
|
"github.com/psviderski/uncloud/cmd/uncloud/wg"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
"github.com/psviderski/uncloud/internal/log"
|
"github.com/psviderski/uncloud/internal/log"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
"github.com/psviderski/uncloud/internal/version"
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type globalOptions struct {
|
type globalOptions struct {
|
||||||
@@ -40,16 +39,10 @@ func main() {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "uc",
|
Use: "uc",
|
||||||
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
|
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
|
||||||
|
Version: version.String(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// Shell completion runs through the hidden __complete command which has flag parsing disabled,
|
|
||||||
// so the global flags from the completed command line are never parsed. Apply them manually to make
|
|
||||||
// completion work with --connect, --context, and --uncloud-config.
|
|
||||||
if cmd.Name() == cobra.ShellCompRequestCmd {
|
|
||||||
applyGlobalFlagsFromCompletionArgs(cmd.Root().PersistentFlags(), os.Args[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
|
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
|
||||||
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
|
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
|
||||||
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
||||||
@@ -88,11 +81,10 @@ func main() {
|
|||||||
|
|
||||||
configPath := fs.ExpandHomeDir(opts.configPath)
|
configPath := fs.ExpandHomeDir(opts.configPath)
|
||||||
|
|
||||||
// Make uc connect via the local Unix socket when running on a cluster machine.
|
|
||||||
if opts.connect == "" {
|
if opts.connect == "" {
|
||||||
if !fs.Exists(configPath) && fs.Exists(machine.DefaultClusterAPISockPath) {
|
if !fs.Exists(configPath) && fs.Exists(machine.DefaultUncloudSockPath) {
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
Unix: machine.DefaultClusterAPISockPath,
|
Unix: machine.DefaultUncloudSockPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,11 +113,15 @@ func main() {
|
|||||||
defaultHelpFunc(c, args)
|
defaultHelpFunc(c, args)
|
||||||
// Only show links for the root 'uc' command.
|
// Only show links for the root 'uc' command.
|
||||||
if c.Name() == "uc" {
|
if c.Name() == "uc" {
|
||||||
|
urlStyle := lipgloss.NewStyle().
|
||||||
|
Underline(true).
|
||||||
|
Foreground(lipgloss.Color("12")) // light blue
|
||||||
|
|
||||||
fmt.Fprintln(c.OutOrStdout())
|
fmt.Fprintln(c.OutOrStdout())
|
||||||
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
|
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
|
||||||
tui.URLStyle.Render(version.DocsURL))
|
urlStyle.Render("https://uncloud.run/docs"))
|
||||||
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
|
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
|
||||||
tui.URLStyle.Render(version.DiscordURL))
|
urlStyle.Render("https://uncloud.run/discord"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,7 +136,6 @@ func main() {
|
|||||||
NewDocsCommand(),
|
NewDocsCommand(),
|
||||||
NewImagesCommand(),
|
NewImagesCommand(),
|
||||||
NewPsCommand(),
|
NewPsCommand(),
|
||||||
NewProxyCommand(),
|
|
||||||
caddy.NewRootCommand(),
|
caddy.NewRootCommand(),
|
||||||
cmdcontext.NewRootCommand(),
|
cmdcontext.NewRootCommand(),
|
||||||
dns.NewRootCommand(),
|
dns.NewRootCommand(),
|
||||||
@@ -156,7 +151,6 @@ func main() {
|
|||||||
service.NewScaleCommand("service"),
|
service.NewScaleCommand("service"),
|
||||||
service.NewStartCommand("service"),
|
service.NewStartCommand("service"),
|
||||||
service.NewStopCommand("service"),
|
service.NewStopCommand("service"),
|
||||||
NewVersionCommand(),
|
|
||||||
volume.NewRootCommand(),
|
volume.NewRootCommand(),
|
||||||
wg.NewRootCommand(),
|
wg.NewRootCommand(),
|
||||||
)
|
)
|
||||||
@@ -168,27 +162,3 @@ func main() {
|
|||||||
cobra.CheckErr(err)
|
cobra.CheckErr(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyGlobalFlagsFromCompletionArgs parses the global flags from the raw arguments of a __complete command and applies
|
|
||||||
// the ones found to flags. The trailing word being completed, unknown flags, and positional arguments are ignored.
|
|
||||||
func applyGlobalFlagsFromCompletionArgs(flags *pflag.FlagSet, args []string) {
|
|
||||||
// The shell always passes the word being completed as the last argument, even if it's empty.
|
|
||||||
// Exclude it from parsing as its value may not be complete yet.
|
|
||||||
if len(args) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
args = args[:len(args)-1]
|
|
||||||
|
|
||||||
fset := pflag.NewFlagSet("global", pflag.ContinueOnError)
|
|
||||||
fset.ParseErrorsAllowlist.UnknownFlags = true
|
|
||||||
fset.String("connect", "", "")
|
|
||||||
fset.StringP("context", "c", "", "")
|
|
||||||
fset.String("uncloud-config", "", "")
|
|
||||||
// Parsing an incomplete command line may fail, apply the flags parsed so far anyway.
|
|
||||||
_ = fset.Parse(args)
|
|
||||||
|
|
||||||
fset.Visit(func(f *pflag.Flag) {
|
|
||||||
// Setting the flag marks it as changed so it takes precedence over environment variables.
|
|
||||||
_ = flags.Set(f.Name, f.Value.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/huh/v2/spinner"
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@@ -82,10 +83,20 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
|||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
var containers []containerInfo
|
var containers []containerInfo
|
||||||
err = tui.RunSpinner(ctx, "Collecting container info...", func(ctx context.Context) error {
|
err = spinner.New().
|
||||||
containers, err = collectContainers(ctx, clusterClient)
|
Title(" Collecting container info...").
|
||||||
return err
|
Type(spinner.MiniDot).
|
||||||
})
|
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
||||||
|
return &spinner.Styles{
|
||||||
|
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
||||||
|
Title: lipgloss.NewStyle(),
|
||||||
|
}
|
||||||
|
})).
|
||||||
|
ActionWithErr(func(ctx context.Context) error {
|
||||||
|
containers, err = collectContainers(ctx, clusterClient)
|
||||||
|
return err
|
||||||
|
}).
|
||||||
|
Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("collect containers: %w", err)
|
return fmt.Errorf("collect containers: %w", err)
|
||||||
}
|
}
|
||||||
@@ -182,12 +193,23 @@ func printContainers(containers []containerInfo) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
|
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
|
||||||
listCtx := client.ProxyMachinesContext(ctx, nil)
|
listCtx, machines, err := cli.ProxyMachinesContext(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy machines context: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map of IP to machine name for resolving response metadata
|
||||||
|
machinesNamesByIP := make(map[string]string)
|
||||||
|
for _, m := range machines {
|
||||||
|
if addr, err := m.Machine.Network.ManagementIp.ToAddr(); err == nil {
|
||||||
|
machinesNamesByIP[addr.String()] = m.Machine.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List all service containers across all machines in the cluster.
|
// List all service containers across all machines in the cluster.
|
||||||
machineContainers, err := cli.Docker.ListServiceContainers(
|
machineContainers, err := cli.Docker.ListServiceContainers(
|
||||||
@@ -199,17 +221,29 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
|
|
||||||
var containers []containerInfo
|
var containers []containerInfo
|
||||||
for _, msc := range machineContainers {
|
for _, msc := range machineContainers {
|
||||||
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
|
// Metadata can be nil if the request was broadcasted to only one machine.
|
||||||
if msc.Metadata == nil {
|
if msc.Metadata == nil && len(machineContainers) > 1 {
|
||||||
tui.PrintWarning("metadata is missing in response from unknown server")
|
return nil, fmt.Errorf("something went wrong with gRPC proxy: metadata is missing for a machine response")
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
machineName := msc.Metadata.MachineName
|
machineName := "unknown"
|
||||||
|
if msc.Metadata != nil {
|
||||||
|
var ok bool
|
||||||
|
machineName, ok = machinesNamesByIP[msc.Metadata.Machine]
|
||||||
|
if !ok {
|
||||||
|
// Fallback to machine's IP as name.
|
||||||
|
machineName = msc.Metadata.Machine
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to the first available machine name.
|
||||||
|
if len(machines) > 0 {
|
||||||
|
machineName = machines[0].Machine.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if msc.Metadata.Error != "" {
|
if msc.Metadata != nil && msc.Metadata.Error != "" {
|
||||||
tui.PrintWarning(fmt.Sprintf("failed to list service containers on machine %s: %s",
|
tui.PrintWarning(fmt.Sprintf("failed to list containers on machine %s: %s", machineName,
|
||||||
machineName, msc.Metadata.Error))
|
msc.Metadata.Error))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockDockerClient implements pb.DockerClient
|
||||||
|
type mockDockerClient struct {
|
||||||
|
pb.DockerClient // Embed to avoid implementing all methods
|
||||||
|
listResp *pb.ListServiceContainersResponse
|
||||||
|
listErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDockerClient) ListServiceContainers(ctx context.Context, in *pb.ListServiceContainersRequest, opts ...grpc.CallOption) (*pb.ListServiceContainersResponse, error) {
|
||||||
|
return m.listResp, m.listErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockClusterClient implements pb.ClusterClient
|
||||||
|
type mockClusterClient struct {
|
||||||
|
pb.ClusterClient // Embed to avoid implementing all methods
|
||||||
|
machinesResp *pb.ListMachinesResponse
|
||||||
|
machinesErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockClusterClient) ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.ListMachinesResponse, error) {
|
||||||
|
return m.machinesResp, m.machinesErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectContainers_NilMetadata(t *testing.T) {
|
||||||
|
// Setup container data
|
||||||
|
containerData := map[string]any{
|
||||||
|
"Id": "container1",
|
||||||
|
"Name": "test-container",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "test-image",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON, err := json.Marshal(containerData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
serviceSpecJSON, err := json.Marshal(map[string]any{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Setup mocks
|
||||||
|
mockDocker := &mockDockerClient{
|
||||||
|
listResp: &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Metadata: nil, // Simulating the issue: nil metadata
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
machineIP := "10.0.0.1"
|
||||||
|
mockCluster := &mockClusterClient{
|
||||||
|
machinesResp: &pb.ListMachinesResponse{
|
||||||
|
Machines: []*pb.MachineMember{
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-1",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr(machineIP)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
State: pb.MachineMember_UP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct client with mocks
|
||||||
|
cli := &client.Client{
|
||||||
|
Docker: &docker.Client{
|
||||||
|
GRPCClient: mockDocker,
|
||||||
|
},
|
||||||
|
ClusterClient: mockCluster,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
containers, err := collectContainers(context.Background(), cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert.Len(t, containers, 1)
|
||||||
|
if len(containers) > 0 {
|
||||||
|
c := containers[0]
|
||||||
|
assert.Equal(t, "container1", c.id)
|
||||||
|
assert.Equal(t, "machine-1", c.machineName, "Should fall back to the single machine name when metadata is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectContainers_NilMetadata_MultipleMachines_Error(t *testing.T) {
|
||||||
|
// If we have multiple machines but receive nil metadata, it should return an error as it is ambiguous
|
||||||
|
|
||||||
|
// Setup container data
|
||||||
|
containerData1 := map[string]any{
|
||||||
|
"Id": "container1",
|
||||||
|
}
|
||||||
|
containerJSON1, _ := json.Marshal(containerData1)
|
||||||
|
|
||||||
|
containerData2 := map[string]any{
|
||||||
|
"Id": "container2",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "test-image",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON2, _ := json.Marshal(containerData2)
|
||||||
|
|
||||||
|
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
||||||
|
|
||||||
|
// Setup mocks
|
||||||
|
mockDocker := &mockDockerClient{
|
||||||
|
listResp: &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Metadata: nil, // Nil metadata
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON1,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: &pb.Metadata{Machine: "10.0.0.2"},
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON2,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockCluster := &mockClusterClient{
|
||||||
|
machinesResp: &pb.ListMachinesResponse{
|
||||||
|
Machines: []*pb.MachineMember{
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-1",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-2",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.2")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct client with mocks
|
||||||
|
cli := &client.Client{
|
||||||
|
Docker: &docker.Client{
|
||||||
|
GRPCClient: mockDocker,
|
||||||
|
},
|
||||||
|
ClusterClient: mockCluster,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
_, err := collectContainers(context.Background(), cli)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "metadata is missing for a machine response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectContainers_MetadataPresent_MultipleMachines(t *testing.T) {
|
||||||
|
// Verify correct mapping of containers to machines when metadata is present
|
||||||
|
|
||||||
|
// Setup container data
|
||||||
|
containerData1 := map[string]any{
|
||||||
|
"Id": "container1",
|
||||||
|
"Name": "container-1",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "image-1",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON1, _ := json.Marshal(containerData1)
|
||||||
|
|
||||||
|
containerData2 := map[string]any{
|
||||||
|
"Id": "container2",
|
||||||
|
"Name": "container-2",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "image-2",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON2, _ := json.Marshal(containerData2)
|
||||||
|
|
||||||
|
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
||||||
|
|
||||||
|
// Setup mocks
|
||||||
|
mockDocker := &mockDockerClient{
|
||||||
|
listResp: &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Metadata: &pb.Metadata{Machine: "10.0.0.1"},
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON1,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: &pb.Metadata{Machine: "10.0.0.2"},
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON2,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockCluster := &mockClusterClient{
|
||||||
|
machinesResp: &pb.ListMachinesResponse{
|
||||||
|
Machines: []*pb.MachineMember{
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-1",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-2",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.2")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := &client.Client{
|
||||||
|
Docker: &docker.Client{GRPCClient: mockDocker},
|
||||||
|
ClusterClient: mockCluster,
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := collectContainers(context.Background(), cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, containers, 2)
|
||||||
|
// Order is not guaranteed by the map iteration in logic or parallel fetch (though here it's mocked sequential),
|
||||||
|
// but collectContainers just appends.
|
||||||
|
// We'll find them by ID.
|
||||||
|
for _, c := range containers {
|
||||||
|
if c.id == "container1" {
|
||||||
|
assert.Equal(t, "machine-1", c.machineName)
|
||||||
|
} else if c.id == "container2" {
|
||||||
|
assert.Equal(t, "machine-2", c.machineName)
|
||||||
|
} else {
|
||||||
|
t.Errorf("unexpected container id: %s", c.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectContainers_NilMetadata_NoMachines(t *testing.T) {
|
||||||
|
// Case: 1 msc with nil metadata but no machines at all
|
||||||
|
|
||||||
|
containerData := map[string]any{
|
||||||
|
"Id": "container1",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "test-image",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON, _ := json.Marshal(containerData)
|
||||||
|
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
||||||
|
|
||||||
|
mockDocker := &mockDockerClient{
|
||||||
|
listResp: &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Metadata: nil,
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// No machines in cluster response
|
||||||
|
mockCluster := &mockClusterClient{
|
||||||
|
machinesResp: &pb.ListMachinesResponse{
|
||||||
|
Machines: []*pb.MachineMember{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := &client.Client{
|
||||||
|
Docker: &docker.Client{GRPCClient: mockDocker},
|
||||||
|
ClusterClient: mockCluster,
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := collectContainers(context.Background(), cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, containers, 1)
|
||||||
|
if len(containers) > 0 {
|
||||||
|
assert.Equal(t, "unknown", containers[0].machineName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectContainers_MetadataPresent_NotInMapping(t *testing.T) {
|
||||||
|
// Case: msc with metadata that is not in the IP-to-name mapping
|
||||||
|
|
||||||
|
containerData := map[string]any{
|
||||||
|
"Id": "container1",
|
||||||
|
"Config": map[string]any{
|
||||||
|
"Image": "test-image",
|
||||||
|
},
|
||||||
|
"State": map[string]any{
|
||||||
|
"Status": "running",
|
||||||
|
"StartedAt": "2023-01-01T12:00:00Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
"NetworkSettings": map[string]any{
|
||||||
|
"Networks": map[string]any{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
containerJSON, _ := json.Marshal(containerData)
|
||||||
|
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
||||||
|
|
||||||
|
mockDocker := &mockDockerClient{
|
||||||
|
listResp: &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Metadata: &pb.Metadata{Machine: "10.0.0.99"}, // Unknown IP
|
||||||
|
Containers: []*pb.ServiceContainer{
|
||||||
|
{
|
||||||
|
Container: containerJSON,
|
||||||
|
ServiceSpec: serviceSpecJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockCluster := &mockClusterClient{
|
||||||
|
machinesResp: &pb.ListMachinesResponse{
|
||||||
|
Machines: []*pb.MachineMember{
|
||||||
|
{
|
||||||
|
Machine: &pb.MachineInfo{
|
||||||
|
Name: "machine-1",
|
||||||
|
Network: &pb.NetworkConfig{
|
||||||
|
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := &client.Client{
|
||||||
|
Docker: &docker.Client{GRPCClient: mockDocker},
|
||||||
|
ClusterClient: mockCluster,
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := collectContainers(context.Background(), cli)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, containers, 1)
|
||||||
|
if len(containers) > 0 {
|
||||||
|
// Should fallback to the IP/string in metadata
|
||||||
|
assert.Equal(t, "10.0.0.99", containers[0].machineName)
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -6,7 +6,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"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"
|
||||||
@@ -55,6 +54,15 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
return fmt.Errorf("inspect service: %w", err)
|
return fmt.Errorf("inspect service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
machines, err := client.ListMachines(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list machines: %w", err)
|
||||||
|
}
|
||||||
|
machinesNamesByID := make(map[string]string)
|
||||||
|
for _, m := range machines {
|
||||||
|
machinesNamesByID[m.Machine.Id] = m.Machine.Name
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Service ID: %s\n", svc.ID)
|
fmt.Printf("Service ID: %s\n", svc.ID)
|
||||||
fmt.Printf("Name: %s\n", svc.Name)
|
fmt.Printf("Name: %s\n", svc.Name)
|
||||||
fmt.Printf("Mode: %s\n", svc.Mode)
|
fmt.Printf("Mode: %s\n", svc.Mode)
|
||||||
@@ -89,7 +97,7 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
for _, ctr := range allContainers {
|
for _, ctr := range allContainers {
|
||||||
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
||||||
|
|
||||||
machine := ctr.MachineName
|
machine := machinesNamesByID[ctr.MachineID]
|
||||||
if machine == "" {
|
if machine == "" {
|
||||||
machine = ctr.MachineID
|
machine = ctr.MachineID
|
||||||
}
|
}
|
||||||
@@ -127,6 +135,6 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
@@ -60,7 +59,7 @@ If no services are specified, streams logs from all services defined in the Comp
|
|||||||
uc logs -m machine1,machine2 web api`,
|
uc logs -m machine1,machine2 web api`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return RunLogs(cmd.Context(), uncli, args, options)
|
return runLogs(cmd.Context(), uncli, args, options)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
@@ -75,19 +74,12 @@ If no services are specified, streams logs from all services defined in the Comp
|
|||||||
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
completion.MachinesFlag(cmd)
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Options) error {
|
func runLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Options) error {
|
||||||
since, until, err := logs.TimeRange(opts.Since, opts.Until, time.Now())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tail, err := opts.TailLines()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceArgs, err := logs.ParseServiceArgs(args)
|
serviceArgs, err := logs.ParseServiceArgs(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -116,6 +108,12 @@ func RunLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse tail option.
|
||||||
|
tail, err := logs.Tail(opts.Tail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
c, err := uncli.ConnectCluster(ctx)
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
@@ -125,8 +123,8 @@ func RunLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Optio
|
|||||||
baseOpts := api.ServiceLogsOptions{
|
baseOpts := api.ServiceLogsOptions{
|
||||||
Follow: opts.Follow,
|
Follow: opts.Follow,
|
||||||
Tail: tail,
|
Tail: tail,
|
||||||
Since: since,
|
Since: opts.Since,
|
||||||
Until: until,
|
Until: opts.Until,
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
@@ -83,6 +82,6 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
t.Row(row...)
|
t.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -93,17 +93,15 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
"Give extended privileges to service containers. This is a security risk and should be used with caution.")
|
"Give extended privileges to service containers. This is a security risk and should be used with caution.")
|
||||||
cmd.Flags().StringSliceVarP(&opts.publish, "publish", "p", nil,
|
cmd.Flags().StringSliceVarP(&opts.publish, "publish", "p", nil,
|
||||||
"Publish a service port to make it accessible outside the cluster. Can be specified multiple times.\n"+
|
"Publish a service port to make it accessible outside the cluster. Can be specified multiple times.\n"+
|
||||||
"Format: [hostname:]container_port[/protocol] or [host_ip|host_prefix:]host_port:container_port[/protocol]@host\n"+
|
"Format: [hostname:]container_port[/protocol] or [host_ip:]host_port:container_port[/protocol]@host\n"+
|
||||||
"Supported protocols: tcp, udp, http, https (default is tcp). If a hostname for http(s) port is not specified\n"+
|
"Supported protocols: tcp, udp, http, https (default is tcp). If a hostname for http(s) port is not specified\n"+
|
||||||
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
|
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
|
||||||
"Examples:\n"+
|
"Examples:\n"+
|
||||||
" -p 8080/https Publish port 8080 as HTTPS via reverse proxy with default service-name.cluster-domain hostname\n"+
|
" -p 8080/https Publish port 8080 as HTTPS via reverse proxy with default service-name.cluster-domain hostname\n"+
|
||||||
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
||||||
// TODO: add support for publishing L4 tcp/udp ports.
|
// TODO: add support for publishing L4 tcp/udp ports.
|
||||||
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
||||||
" -p 53:5353/udp@host Bind UDP port 5353 to host port 53\n"+
|
" -p 53:5353/udp@host Bind UDP port 5353 to host port 53")
|
||||||
" -p 192.168.76.0/24:53:5353/udp@host Bind UDP port 5353 to host port 53 on every host IP address\n"+
|
|
||||||
" contained in the prefix 192.168.76.0/24")
|
|
||||||
cmd.Flags().StringVar(&opts.pull, "pull", api.PullPolicyMissing,
|
cmd.Flags().StringVar(&opts.pull, "pull", api.PullPolicyMissing,
|
||||||
fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').",
|
fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').",
|
||||||
api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever))
|
api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever))
|
||||||
@@ -122,7 +120,6 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
"Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n"+
|
"Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n"+
|
||||||
"the volume is located. Can be specified multiple times.\n"+
|
"the volume is located. Can be specified multiple times.\n"+
|
||||||
"Format: volume_name:/container/path[:ro|volume-nocopy] or /host/path:/container/path[:ro]\n"+
|
"Format: volume_name:/container/path[:ro|volume-nocopy] or /host/path:/container/path[:ro]\n"+
|
||||||
"Host and container paths support runtime templates such as {{.Container.Name}}.\n"+
|
|
||||||
"Examples:\n"+
|
"Examples:\n"+
|
||||||
" -v postgres-data:/var/lib/postgresql/data Mount volume 'postgres-data' to /var/lib/postgresql/data in container\n"+
|
" -v postgres-data:/var/lib/postgresql/data Mount volume 'postgres-data' to /var/lib/postgresql/data in container\n"+
|
||||||
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
|
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
|
||||||
@@ -30,11 +30,9 @@ func NewScaleCommand(groupID string) *cobra.Command {
|
|||||||
Short: "Scale a replicated service by changing the number of replicas.",
|
Short: "Scale a replicated service by changing the number of replicas.",
|
||||||
Long: "Scale a replicated service by changing the number of replicas.",
|
Long: "Scale a replicated service by changing the number of replicas.",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
opts.service = args[0]
|
opts.service = args[0]
|
||||||
@@ -138,7 +136,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
|
|||||||
|
|
||||||
// Ask for confirmation unless auto-confirmed with --yes.
|
// Ask for confirmation unless auto-confirmed with --yes.
|
||||||
if !opts.yes {
|
if !opts.yes {
|
||||||
if !tui.IsTerminalAvailable() {
|
if !tui.IsStdinTerminal() {
|
||||||
return errors.New("cannot ask to confirm scaling plan in non-interactive mode, " +
|
return errors.New("cannot ask to confirm scaling plan in non-interactive mode, " +
|
||||||
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
File renamed without changes.
@@ -6,7 +6,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
@@ -96,6 +95,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
t.Row(v.Volume.Name, v.Volume.Driver, v.MachineName)
|
t.Row(v.Volume.Name, v.Volume.Driver, v.MachineName)
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,8 @@ func remove(ctx context.Context, uncli *cli.CLI, names []string, opts removeOpti
|
|||||||
return fmt.Errorf("confirm removal: %w", err)
|
return fmt.Errorf("confirm removal: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
return cli.Cancelled("Cancelled. No volumes were removed.")
|
fmt.Println("Cancelled. No volumes were removed.")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -6,12 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -61,7 +60,10 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
|
|
||||||
if opts.machine != "" {
|
if opts.machine != "" {
|
||||||
// Proxy requests to the specified machine.
|
// Proxy requests to the specified machine.
|
||||||
ctx = client.ProxySingleMachineContext(ctx, opts.machine)
|
ctx, _, err = client.ProxyMachinesContext(ctx, []string{opts.machine})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.MachineClient.InspectWireGuardNetwork(ctx, nil)
|
resp, err := client.MachineClient.InspectWireGuardNetwork(ctx, nil)
|
||||||
@@ -134,6 +136,6 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
lipgloss.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
_ _ _
|
|
||||||
_ _ _ __ ___| | ___ _ _ __| | __| |
|
|
||||||
| | | | '_ \ / __| |/ _ \| | | |/ _` |/ _` |
|
|
||||||
| |_| | | | | (__| | (_) | |_| | (_| | (_| |
|
|
||||||
\__,_|_| |_|\___|_|\___/ \__,_|\__,_|\__,_|
|
|
||||||
@@ -23,7 +23,7 @@ func newDialStdioCommand() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultClusterAPISockPath,
|
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultUncloudSockPath,
|
||||||
"Path to the Uncloud API socket")
|
"Path to the Uncloud API socket")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/daemon"
|
"github.com/psviderski/uncloud/internal/daemon"
|
||||||
"github.com/psviderski/uncloud/internal/log"
|
"github.com/psviderski/uncloud/internal/log"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
"github.com/psviderski/uncloud/internal/metrics"
|
|
||||||
"github.com/psviderski/uncloud/internal/version"
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -26,7 +24,7 @@ func main() {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "uncloudd",
|
Use: "uncloudd",
|
||||||
Short: "Uncloud machine daemon.",
|
Short: "Uncloud machine daemon.",
|
||||||
Long: "Uncloud machine daemon.\n" + tui.URLStyle.Render(version.WebsiteURL),
|
Version: version.String(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -41,13 +39,11 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.PersistentFlags().StringVarP(&dataDir, "data-dir", "d", machine.DefaultDataDir,
|
cmd.PersistentFlags().StringVarP(&dataDir, "data-dir", "d", machine.DefaultDataDir,
|
||||||
"Directory for storing persistent machine state.")
|
"Directory for storing persistent machine state")
|
||||||
_ = cmd.MarkFlagDirname("data-dir")
|
_ = cmd.MarkFlagDirname("data-dir")
|
||||||
|
|
||||||
|
// Add dial-stdio subcommand.
|
||||||
cmd.AddCommand(newDialStdioCommand())
|
cmd.AddCommand(newDialStdioCommand())
|
||||||
cmd.AddCommand(newVersionCommand())
|
|
||||||
|
|
||||||
metrics.Version.WithLabelValues(version.String()).Set(1)
|
|
||||||
|
|
||||||
// ctx is canceled when the daemon command is interrupted.
|
// ctx is canceled when the daemon command is interrupted.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/internal/version"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed art.txt
|
|
||||||
var asciiArt string
|
|
||||||
|
|
||||||
// newVersionCommand creates a new command to print the version and build information for the binary.
|
|
||||||
func newVersionCommand() *cobra.Command {
|
|
||||||
var output string
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "version",
|
|
||||||
Short: "Show version and build information.",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
info := version.GetInfo()
|
|
||||||
w := cmd.OutOrStdout()
|
|
||||||
|
|
||||||
switch output {
|
|
||||||
case "":
|
|
||||||
fmt.Fprint(w, humanVersion(info))
|
|
||||||
case "json":
|
|
||||||
s, err := info.JSONString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, s)
|
|
||||||
default:
|
|
||||||
s, err := templateVersion(output, info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&output, "output", "o", "",
|
|
||||||
"Output format: 'json' or a Go template (e.g., '{{.Version}}').\n"+
|
|
||||||
"Run with '-o json' to discover the field names available to the template.\n"+
|
|
||||||
"(default is human-readable)")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanVersion(info version.Info) string {
|
|
||||||
var b strings.Builder
|
|
||||||
b.WriteString(asciiArt)
|
|
||||||
b.WriteString("\n")
|
|
||||||
b.WriteString(fmt.Sprintf("uncloudd: Uncloud machine daemon (%s)",
|
|
||||||
tui.URLStyle.Render(version.WebsiteURL)))
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
b.WriteString(info.String())
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateVersion renders the version info using the provided Go template.
|
|
||||||
func templateVersion(tmpl string, info version.Info) (string, error) {
|
|
||||||
t, err := template.New("version").Parse(tmpl)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("parse template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err = t.Execute(&buf, info); err != nil {
|
|
||||||
return "", fmt.Errorf("execute template: %w", err)
|
|
||||||
}
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
||||||
+22
-27
@@ -1,6 +1,6 @@
|
|||||||
module github.com/psviderski/uncloud/experiment
|
module github.com/psviderski/uncloud/experiment
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.5
|
github.com/dgraph-io/badger/v3 v3.2103.5
|
||||||
@@ -18,22 +18,17 @@ require (
|
|||||||
github.com/psviderski/uncloud v0.0.0
|
github.com/psviderski/uncloud v0.0.0
|
||||||
github.com/siderolabs/discovery-api v0.1.4
|
github.com/siderolabs/discovery-api v0.1.4
|
||||||
github.com/siderolabs/discovery-client v0.1.9
|
github.com/siderolabs/discovery-client v0.1.9
|
||||||
go.uber.org/zap v1.28.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/net v0.59.0
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
|
||||||
google.golang.org/grpc v1.81.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
|
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/armon/go-radix v1.0.0 // indirect
|
github.com/armon/go-radix v1.0.0 // indirect
|
||||||
github.com/bgentry/speakeasy v0.2.0 // indirect
|
github.com/bgentry/speakeasy v0.2.0 // indirect
|
||||||
github.com/caarlos0/go-version v0.2.2 // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@@ -79,18 +74,18 @@ require (
|
|||||||
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
||||||
github.com/jbenet/goprocess v0.1.4 // indirect
|
github.com/jbenet/goprocess v0.1.4 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.19.1 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||||
github.com/libp2p/go-libp2p v0.35.4 // indirect
|
github.com/libp2p/go-libp2p v0.35.4 // indirect
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.11.0 // indirect
|
github.com/libp2p/go-libp2p-pubsub v0.11.0 // indirect
|
||||||
github.com/libp2p/go-msgio v0.3.0 // indirect
|
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/miekg/dns v1.1.73 // indirect
|
github.com/miekg/dns v1.1.65 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/mitchellh/cli v1.1.5 // indirect
|
github.com/mitchellh/cli v1.1.5 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
@@ -113,32 +108,32 @@ require (
|
|||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/siderolabs/gen v0.4.8 // indirect
|
github.com/siderolabs/gen v0.4.8 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/spf13/cast v1.7.0 // indirect
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/crypto v0.57.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/mod v0.41.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.23.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/sys v0.48.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/text v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/time v0.16.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
golang.org/x/tools v0.50.0 // indirect
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||||
google.golang.org/protobuf v1.36.12 // indirect
|
google.golang.org/grpc v1.74.2 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gotest.tools/v3 v3.5.2 // indirect
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+4
-35
@@ -5,8 +5,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
@@ -40,8 +38,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
|
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
|
||||||
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/caarlos0/go-version v0.2.2 h1:5r+nlrg4H2wOVwWjqRqRRIRbZ7ytRmjC9xoMIP0a5kQ=
|
|
||||||
github.com/caarlos0/go-version v0.2.2/go.mod h1:X+rI5VAtJDpcjCjeEIXpxGa5+rTcgur1FK66wS0/944=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@@ -302,10 +298,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||||
@@ -351,7 +345,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
|||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
@@ -371,7 +364,6 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
|
|||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||||
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
||||||
@@ -533,7 +525,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
|
||||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
@@ -586,20 +577,16 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
|
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
|
||||||
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||||
@@ -616,7 +603,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
|||||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
@@ -632,7 +618,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
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 v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
@@ -646,7 +631,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -666,7 +650,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -677,9 +660,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -712,9 +694,8 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@@ -726,10 +707,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
|
||||||
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -744,13 +721,10 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
@@ -763,7 +737,6 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
@@ -772,7 +745,6 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
|||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -784,7 +756,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -805,8 +776,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func TestDiscovery() error {
|
|||||||
}
|
}
|
||||||
endpoints[i] = &pb.Endpoint{
|
endpoints[i] = &pb.Endpoint{
|
||||||
Ip: ip,
|
Ip: ip,
|
||||||
Port: network.DefaultWireGuardPort,
|
Port: network.WireGuardPort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = client.SetLocalData(
|
if err = client.SetLocalData(
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
module github.com/psviderski/uncloud
|
module github.com/psviderski/uncloud
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/bubbles/v2 v2.0.0
|
charm.land/bubbles/v2 v2.0.0
|
||||||
charm.land/bubbletea/v2 v2.0.10
|
charm.land/bubbletea/v2 v2.0.2
|
||||||
charm.land/huh/v2 v2.0.1
|
charm.land/huh/v2 v2.0.1
|
||||||
charm.land/lipgloss/v2 v2.0.1
|
charm.land/lipgloss/v2 v2.0.1
|
||||||
github.com/BurntSushi/toml v1.6.0
|
github.com/BurntSushi/toml v1.5.0
|
||||||
github.com/Masterminds/semver v1.5.0
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/Masterminds/squirrel v1.5.4
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
github.com/alecthomas/chroma/v2 v2.24.1
|
github.com/alecthomas/chroma/v2 v2.20.0
|
||||||
github.com/caarlos0/go-version v0.2.2
|
github.com/caddyserver/caddy/v2 v2.8.4
|
||||||
github.com/caddyserver/caddy/v2 v2.11.4
|
|
||||||
github.com/caddyserver/certmagic v0.25.4
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0
|
github.com/cenkalti/backoff/v4 v4.3.0
|
||||||
github.com/charmbracelet/colorprofile v0.4.3
|
github.com/charmbracelet/colorprofile v0.4.2
|
||||||
github.com/charmbracelet/x/ansi v0.11.7
|
github.com/charmbracelet/x/ansi v0.11.6
|
||||||
github.com/compose-spec/compose-go/v2 v2.9.0
|
github.com/compose-spec/compose-go/v2 v2.9.0
|
||||||
github.com/containerd/errdefs v1.0.0
|
github.com/containerd/errdefs v1.0.0
|
||||||
github.com/containerd/platforms v1.0.0-rc.1
|
github.com/containerd/platforms v1.0.0-rc.1
|
||||||
@@ -31,72 +29,78 @@ require (
|
|||||||
github.com/goccy/go-yaml v1.17.1
|
github.com/goccy/go-yaml v1.17.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/go-containerregistry v0.20.2
|
github.com/google/go-containerregistry v0.20.2
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/miekg/dns v1.1.65
|
||||||
github.com/mholt/acmez/v3 v3.1.6
|
|
||||||
github.com/miekg/dns v1.1.73
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/moby/term v0.5.2
|
github.com/moby/term v0.5.2
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/prometheus/client_golang v1.24.1
|
|
||||||
github.com/psviderski/unregistry v0.4.1
|
github.com/psviderski/unregistry v0.4.1
|
||||||
github.com/siderolabs/grpc-proxy v0.5.1
|
github.com/siderolabs/grpc-proxy v0.5.1
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.1
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/net v0.59.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.19.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.42.0
|
||||||
golang.org/x/term v0.46.0
|
golang.org/x/term v0.34.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a
|
||||||
google.golang.org/grpc v1.81.0
|
google.golang.org/grpc v1.74.2
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.9
|
||||||
modernc.org/sqlite v1.49.1
|
modernc.org/sqlite v1.36.3
|
||||||
tags.cncf.io/container-device-interface v1.0.1
|
tags.cncf.io/container-device-interface v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/kms v1.19.0 // indirect
|
||||||
|
cloud.google.com/go/longrunning v0.6.0 // indirect
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||||
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect
|
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect
|
||||||
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
|
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.13.0 // indirect
|
github.com/Microsoft/hcsshim v0.13.0 // indirect
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
|
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
|
github.com/aws/smithy-go v1.20.3 // indirect
|
||||||
github.com/aws/smithy-go v1.25.1 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/buger/goterm v1.0.4 // indirect
|
github.com/buger/goterm v1.0.4 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
github.com/caddyserver/certmagic v0.21.4 // indirect
|
||||||
|
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||||
github.com/catppuccin/go v0.3.0 // indirect
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
|
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
||||||
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||||
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
||||||
@@ -113,9 +117,13 @@ require (
|
|||||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||||
|
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||||
|
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/distribution/distribution/v3 v3.0.0 // indirect
|
github.com/distribution/distribution/v3 v3.0.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/docker/buildx v0.29.1 // indirect
|
github.com/docker/buildx v0.29.1 // indirect
|
||||||
github.com/docker/cli-docs-tool v0.10.0 // indirect
|
github.com/docker/cli-docs-tool v0.10.0 // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
@@ -129,59 +137,86 @@ require (
|
|||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsevents v0.2.0 // indirect
|
github.com/fsnotify/fsevents v0.2.0 // indirect
|
||||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||||
|
github.com/go-kit/kit v0.13.0 // indirect
|
||||||
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/gofrs/flock v0.12.1 // indirect
|
github.com/gofrs/flock v0.12.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||||
|
github.com/golang/glog v1.2.5 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/cel-go v0.20.1 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/handlers v1.5.2 // indirect
|
github.com/gorilla/handlers v1.5.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
|
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
|
||||||
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
|
github.com/jackc/pgconn v1.14.3 // indirect
|
||||||
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgtype v1.14.0 // indirect
|
||||||
|
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.19.1 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/libdns/libdns v1.1.1 // indirect
|
github.com/libdns/libdns v0.2.2 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.2 // indirect
|
github.com/mholt/acmez/v2 v2.0.3 // indirect
|
||||||
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/buildkit v0.25.0 // indirect
|
github.com/moby/buildkit v0.25.0 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/go-archive v0.1.0 // indirect
|
github.com/moby/go-archive v0.1.0 // indirect
|
||||||
@@ -202,36 +237,46 @@ require (
|
|||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/prometheus/client_model v0.6.3 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/common v0.71.0 // indirect
|
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/procfs v0.22.0 // indirect
|
github.com/prometheus/common v0.62.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.62.0 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect
|
||||||
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect
|
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/rs/xid v1.5.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/slackhq/nebula v1.6.1 // indirect
|
||||||
github.com/spf13/viper v1.12.0 // indirect
|
github.com/smallstep/certificates v0.26.1 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/smallstep/nosql v0.6.1 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/smallstep/pkcs7 v0.1.1 // indirect
|
||||||
|
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
||||||
|
github.com/smallstep/truststore v0.13.0 // indirect
|
||||||
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
|
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
|
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
|
||||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||||
@@ -239,6 +284,7 @@ require (
|
|||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||||
|
github.com/urfave/cli v1.22.17 // indirect
|
||||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
@@ -246,57 +292,67 @@ require (
|
|||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
|
go.etcd.io/bbolt v1.4.3 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect
|
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect
|
||||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 // indirect
|
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect
|
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
|
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
go.opentelemetry.io/otel/log v0.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||||
|
go.step.sm/cli-utils v0.9.0 // indirect
|
||||||
|
go.step.sm/crypto v0.45.0 // indirect
|
||||||
|
go.step.sm/linkedca v0.20.1 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||||
go.uber.org/mock v0.6.0 // indirect
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.28.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.2.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
|
||||||
golang.org/x/text v0.42.0 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/time v0.16.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/tools v0.50.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||||
|
howett.net/plist v1.0.0 // indirect
|
||||||
k8s.io/api v0.32.3 // indirect
|
k8s.io/api v0.32.3 // indirect
|
||||||
k8s.io/apimachinery v0.32.3 // indirect
|
k8s.io/apimachinery v0.32.3 // indirect
|
||||||
k8s.io/client-go v0.32.3 // indirect
|
k8s.io/client-go v0.32.3 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
modernc.org/libc v1.61.13 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.8.2 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
|
|||||||
+14
-48
@@ -8,11 +8,9 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
|
||||||
"github.com/psviderski/uncloud/internal/sshexec"
|
"github.com/psviderski/uncloud/internal/sshexec"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
@@ -176,8 +174,6 @@ type InitClusterOptions struct {
|
|||||||
Version string
|
Version string
|
||||||
AutoConfirm bool
|
AutoConfirm bool
|
||||||
WireguardEndpoints []*pb.IPPort
|
WireguardEndpoints []*pb.IPPort
|
||||||
WireguardMTU int
|
|
||||||
WireguardPort int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitCluster initialises a new cluster on a remote machine and returns a client to interact with the cluster.
|
// InitCluster initialises a new cluster on a remote machine and returns a client to interact with the cluster.
|
||||||
@@ -238,8 +234,6 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
|
|||||||
MachineName: opts.MachineName,
|
MachineName: opts.MachineName,
|
||||||
Network: pb.NewIPPrefix(opts.Network),
|
Network: pb.NewIPPrefix(opts.Network),
|
||||||
WireguardEndpoints: opts.WireguardEndpoints,
|
WireguardEndpoints: opts.WireguardEndpoints,
|
||||||
WireguardMtu: int32(opts.WireguardMTU),
|
|
||||||
WireguardPort: int32(opts.WireguardPort),
|
|
||||||
}
|
}
|
||||||
if opts.PublicIP != nil {
|
if opts.PublicIP != nil {
|
||||||
if opts.PublicIP.IsValid() {
|
if opts.PublicIP.IsValid() {
|
||||||
@@ -323,15 +317,13 @@ type AddMachineOptions struct {
|
|||||||
Version string
|
Version string
|
||||||
AutoConfirm bool
|
AutoConfirm bool
|
||||||
WireguardEndpoints []*pb.IPPort
|
WireguardEndpoints []*pb.IPPort
|
||||||
WireguardMTU int
|
|
||||||
WireguardPort int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMachine provisions a remote machine and adds it to the cluster. It returns a cluster client and a machine client.
|
// AddMachine provisions a remote machine and adds it to the cluster. It returns a cluster client and a machine client.
|
||||||
// The cluster client is connected to the existing machine in the cluster. It was used to add the new machine to the
|
// The cluster client is connected to the existing machine in the cluster. It was used to add the new machine to the
|
||||||
// cluster. The machine client is connected to the new machine and can be used to interact with it.
|
// cluster. The machine client is connected to the new machine and can be used to interact with it.
|
||||||
// Both client should be closed after use by the caller.
|
// Both client should be closed after use by the caller.
|
||||||
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *client.Client, _ *client.Client, err error) {
|
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, *client.Client, error) {
|
||||||
contextName := cli.ContextOverrideOrCurrent()
|
contextName := cli.ContextOverrideOrCurrent()
|
||||||
c, err := cli.ConnectCluster(ctx)
|
c, err := cli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -352,19 +344,16 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *clie
|
|||||||
machineClient.Close()
|
machineClient.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// IMPORTANT: 'err' is a named return value so the deferred cleanups above observe it on every error return.
|
|
||||||
// Do not shadow it with ':=' in a nested scope, or the deferred client cleanup would be skipped on error.
|
|
||||||
|
|
||||||
// Check if the machine is already initialised as a cluster member and prompt the user to reset it first.
|
// Check if the machine is already initialised as a cluster member and prompt the user to reset it first.
|
||||||
inspectResp, err := machineClient.MachineClient.InspectMachine(ctx, nil)
|
// TODO: refactor to use client.InspectMachine.
|
||||||
|
minfo, err := machineClient.Inspect(ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("inspect machine: %w", err)
|
return nil, nil, fmt.Errorf("inspect machine: %w", err)
|
||||||
}
|
}
|
||||||
minfo := inspectResp.Machines[0].Machine
|
|
||||||
if minfo.Id != "" {
|
if minfo.Id != "" {
|
||||||
// Check if the machine is already a member of this cluster.
|
// Check if the machine is already a member of this cluster.
|
||||||
var machines api.MachineMembersList
|
machines, err := c.ListMachines(ctx, nil)
|
||||||
machines, err = c.ListMachines(ctx, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
||||||
}
|
}
|
||||||
@@ -412,32 +401,11 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *clie
|
|||||||
} else {
|
} else {
|
||||||
endpoints = make([]*pb.IPPort, len(token.Endpoints))
|
endpoints = make([]*pb.IPPort, len(token.Endpoints))
|
||||||
for i, addrPort := range token.Endpoints {
|
for i, addrPort := range token.Endpoints {
|
||||||
// If a custom WireGuard port is specified, override the port from the token endpoints
|
|
||||||
// since the token was generated before the machine knows its configured port.
|
|
||||||
if opts.WireguardPort != 0 && opts.WireguardPort != network.DefaultWireGuardPort {
|
|
||||||
addrPort = netip.AddrPortFrom(addrPort.Addr(), uint16(opts.WireguardPort))
|
|
||||||
}
|
|
||||||
endpoints[i] = pb.NewIPPort(addrPort)
|
endpoints[i] = pb.NewIPPort(addrPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Default the machine name to the machine's hostname when not explicitly provided, ensuring it is
|
|
||||||
// unique within the cluster.
|
|
||||||
machineName := opts.MachineName
|
|
||||||
if machineName == "" {
|
|
||||||
var machines api.MachineMembersList
|
|
||||||
if machines, err = c.ListMachines(ctx, nil); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
|
||||||
}
|
|
||||||
existing := make([]string, len(machines))
|
|
||||||
for i, m := range machines {
|
|
||||||
existing[i] = m.Machine.Name
|
|
||||||
}
|
|
||||||
if machineName, err = cluster.DefaultMachineName(minfo.Hostname, existing); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("generate machine name: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addReq := &pb.AddMachineRequest{
|
addReq := &pb.AddMachineRequest{
|
||||||
Name: machineName,
|
Name: opts.MachineName,
|
||||||
Network: &pb.NetworkConfig{
|
Network: &pb.NetworkConfig{
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
PublicKey: token.PublicKey,
|
PublicKey: token.PublicKey,
|
||||||
@@ -457,16 +425,16 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *clie
|
|||||||
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
|
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot the cluster store version so the new machine can catch up before participating.
|
// Get the current store DB version from the cluster to pass to the join request.
|
||||||
var storeVersion api.StoreVersion
|
var storeDBVersion int64
|
||||||
inspectResp, err = c.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
inspectResp, err := c.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(lhf): remove Unimplemented check when v0.17.0 is released.
|
// TODO(lhf): remove Unimplemented check when v0.17.0 is released.
|
||||||
if status.Convert(err).Code() != codes.Unimplemented {
|
if status.Convert(err).Code() != codes.Unimplemented {
|
||||||
return nil, nil, fmt.Errorf("inspect current cluster machine: %w", err)
|
return nil, nil, fmt.Errorf("inspect current cluster machine: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
storeVersion = inspectResp.Machines[0].StoreVersion
|
storeDBVersion = inspectResp.Machines[0].StoreDbVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the most up-to-date list of other machines in the cluster to include them in the join request.
|
// Get the most up-to-date list of other machines in the cluster to include them in the join request.
|
||||||
@@ -483,11 +451,9 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *clie
|
|||||||
|
|
||||||
// Configure the remote machine to join the cluster.
|
// Configure the remote machine to join the cluster.
|
||||||
joinReq := &pb.JoinClusterRequest{
|
joinReq := &pb.JoinClusterRequest{
|
||||||
Machine: addResp.Machine,
|
Machine: addResp.Machine,
|
||||||
OtherMachines: otherMachines,
|
OtherMachines: otherMachines,
|
||||||
MinStoreVersion: storeVersion,
|
MinStoreDbVersion: storeDBVersion,
|
||||||
WireguardMtu: int32(opts.WireguardMTU),
|
|
||||||
WireguardPort: int32(opts.WireguardPort),
|
|
||||||
}
|
}
|
||||||
if _, err = machineClient.JoinCluster(ctx, joinReq); err != nil {
|
if _, err = machineClient.JoinCluster(ctx, joinReq); err != nil {
|
||||||
return nil, nil, fmt.Errorf("join cluster: %w", err)
|
return nil, nil, fmt.Errorf("join cluster: %w", err)
|
||||||
@@ -603,7 +569,7 @@ func provisionOrConnectRemoteMachine(
|
|||||||
if remoteMachine.User != rootUser {
|
if remoteMachine.User != rootUser {
|
||||||
// provisionMachine has just added the user to the uncloud group. Any SSH ControlMaster left over from
|
// provisionMachine has just added the user to the uncloud group. Any SSH ControlMaster left over from
|
||||||
// a previous uc invocation (e.g. a failed uc command against the uninitialised machine) still holds
|
// a previous uc invocation (e.g. a failed uc command against the uninitialised machine) still holds
|
||||||
// the old user groups and would deny access to /run/uncloud/api/uncloud.sock. Close the current session
|
// the old user groups and would deny access to /run/uncloud/uncloud.sock. Close the current session
|
||||||
// if it exists so the next session picks up the new groups.
|
// if it exists so the next session picks up the new groups.
|
||||||
conn.CloseControlMaster(ctx)
|
conn.CloseControlMaster(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
// There are no contexts to complete when the CLI uses a direct machine connection (--connect) without a config.
|
|
||||||
if uncli.Config == nil {
|
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
}
|
|
||||||
|
|
||||||
contexts := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
contexts := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
||||||
|
|
||||||
names := []cobra.Completion{}
|
names := []cobra.Completion{}
|
||||||
@@ -26,6 +21,7 @@ func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete str
|
|||||||
if strings.HasPrefix(context, toComplete) {
|
if strings.HasPrefix(context, toComplete) {
|
||||||
names = append(names, context)
|
names = append(names, context)
|
||||||
}
|
}
|
||||||
|
names = append(names, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
return names, cobra.ShellCompDirectiveNoFileComp
|
return names, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Machines(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
func Machines(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
// Disable the connection progress output to not interfere with the shell completion output.
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Services(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
func Services(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
// Disable the connection progress output to not interfere with the shell completion output.
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Volumes(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
func Volumes(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
// Disable the connection progress output to not interfere with the shell completion output.
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MachineConnection struct {
|
type MachineConnection struct {
|
||||||
@@ -21,8 +23,10 @@ type MachineConnection struct {
|
|||||||
// The pointer is used to omit the field when not set. Otherwise, yaml marshalling includes an empty object.
|
// The pointer is used to omit the field when not set. Otherwise, yaml marshalling includes an empty object.
|
||||||
TCP *netip.AddrPort `yaml:"tcp,omitempty"`
|
TCP *netip.AddrPort `yaml:"tcp,omitempty"`
|
||||||
// Unix is the path to the machine's API unix socket.
|
// Unix is the path to the machine's API unix socket.
|
||||||
Unix string `yaml:"unix,omitempty"`
|
Unix string `yaml:"unix,omitempty"`
|
||||||
MachineID string `yaml:"machine_id,omitempty"`
|
Host string `yaml:"host,omitempty"`
|
||||||
|
PublicKey secret.Secret `yaml:"public_key,omitempty"`
|
||||||
|
MachineID string `yaml:"machine_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MachineConnection) String() string {
|
func (c *MachineConnection) String() string {
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ func TestMachineConnection_String(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "unix connection",
|
name: "unix connection",
|
||||||
conn: MachineConnection{
|
conn: MachineConnection{
|
||||||
Unix: "/run/uncloud/api/uncloud.sock",
|
Unix: "/run/uncloud/uncloud.sock",
|
||||||
},
|
},
|
||||||
want: "unix:///run/uncloud/api/uncloud.sock",
|
want: "unix:///run/uncloud/uncloud.sock",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no connection",
|
name: "no connection",
|
||||||
|
|||||||
+12
-14
@@ -31,9 +31,10 @@ func ConnectCluster(ctx context.Context, conn config.MachineConnection, opts Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// connectClusterWithProgress connects to the cluster while displaying a progress spinner.
|
// connectClusterWithProgress connects to the cluster while displaying a progress spinner.
|
||||||
// If a terminal is not available, it falls back to simple progress logs to stderr.
|
// If the stdout is not a terminal, it falls back to simple progress logs to stderr.
|
||||||
func connectClusterWithProgress(ctx context.Context, conn config.MachineConnection) (*client.Client, error) {
|
func connectClusterWithProgress(ctx context.Context, conn config.MachineConnection) (*client.Client, error) {
|
||||||
if !tui.IsTerminalAvailable() {
|
// If stdout is not a terminal, fall back to simple progress logs.
|
||||||
|
if !tui.IsStdoutTerminal() {
|
||||||
fmt.Fprintln(os.Stderr, "Connecting to", conn.String())
|
fmt.Fprintln(os.Stderr, "Connecting to", conn.String())
|
||||||
cli, err := connectCluster(ctx, conn)
|
cli, err := connectCluster(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,18 +45,8 @@ func connectClusterWithProgress(ctx context.Context, conn config.MachineConnecti
|
|||||||
return cli, err
|
return cli, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the connection TUI model. Render to stderr so stdout stays clean for command output.
|
// Run the connection TUI model.
|
||||||
p := tea.NewProgram(newConnectModel(ctx, conn),
|
p := tea.NewProgram(newConnectModel(ctx, conn))
|
||||||
tea.WithOutput(os.Stderr),
|
|
||||||
tea.WithInput(nil),
|
|
||||||
// Ctrl+C handling
|
|
||||||
tea.WithFilter(func(_ tea.Model, msg tea.Msg) tea.Msg {
|
|
||||||
if _, ok := msg.(tea.InterruptMsg); ok {
|
|
||||||
return connectResultMsg{err: errors.New("connection cancelled")}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
model, err := p.Run()
|
model, err := p.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("run connection TUI: %w", err)
|
return nil, fmt.Errorf("run connection TUI: %w", err)
|
||||||
@@ -200,6 +191,13 @@ func (m connectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.spinner, cmd = m.spinner.Update(msg)
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
return m, cmd
|
return m, cmd
|
||||||
|
|
||||||
|
case tea.KeyPressMsg:
|
||||||
|
if msg.String() == "ctrl+c" {
|
||||||
|
m.result.err = fmt.Errorf("connection cancelled")
|
||||||
|
m.done = true
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,18 +40,18 @@ func BindEnvToFlag(cmd *cobra.Command, flagName, envVar string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseWireGuardEndpoints parses a list of endpoint strings into a list of IPPort protobuf messages. Each value can
|
// ParseWireGuardEndpoints parses a list of endpoint strings into a list of IPPort protobuf messages. Each value can
|
||||||
// be an IP address, IP:PORT, IPv6, or [IPv6]:PORT. If the port is omitted, defaultPort is used.
|
// be an IP address, IP:PORT, IPv6, or [IPv6]:PORT. If the port is omitted, the default WireGuard port is used.
|
||||||
func ParseWireGuardEndpoints(values []string, defaultPort uint16) ([]*pb.IPPort, error) {
|
func ParseWireGuardEndpoints(values []string) ([]*pb.IPPort, error) {
|
||||||
endpoints := make([]*pb.IPPort, 0, len(values))
|
endpoints := make([]*pb.IPPort, 0, len(values))
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
ap, err := netip.ParseAddrPort(v)
|
ap, err := netip.ParseAddrPort(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try parsing as a bare IP address and use the provided default port.
|
// Try parsing as a bare IP address and use the default WireGuard port.
|
||||||
addr, addrErr := netip.ParseAddr(v)
|
addr, addrErr := netip.ParseAddr(v)
|
||||||
if addrErr != nil {
|
if addrErr != nil {
|
||||||
return nil, fmt.Errorf("invalid endpoint '%s': must be IP, IPv6, IP:PORT, or [IPv6]:PORT", v)
|
return nil, fmt.Errorf("invalid endpoint '%s': must be IP, IPv6, IP:PORT, or [IPv6]:PORT", v)
|
||||||
}
|
}
|
||||||
ap = netip.AddrPortFrom(addr, defaultPort)
|
ap = netip.AddrPortFrom(addr, network.WireGuardPort)
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints, pb.NewIPPort(ap))
|
endpoints = append(endpoints, pb.NewIPPort(ap))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Duration is an extended standard time.Duration that also supports days as a unit.
|
|
||||||
type Duration = time.Duration
|
|
||||||
|
|
||||||
// ParseDuration parses a Go duration with the additional unit d, meaning exactly 24 hours.
|
|
||||||
// Days can be fractional or combined with other units, such as "1.5d" or "2d3h".
|
|
||||||
func ParseDuration(value string) (Duration, error) {
|
|
||||||
if !strings.Contains(value, "d") {
|
|
||||||
return time.ParseDuration(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
rest := value
|
|
||||||
var normalised strings.Builder
|
|
||||||
if len(rest) > 0 && (rest[0] == '-' || rest[0] == '+') {
|
|
||||||
normalised.WriteByte(rest[0])
|
|
||||||
rest = rest[1:]
|
|
||||||
}
|
|
||||||
for len(rest) > 0 {
|
|
||||||
// Each component is a decimal number followed by a unit. Only the leading sign is allowed.
|
|
||||||
numberEnd := 0
|
|
||||||
for numberEnd < len(rest) && (isDigit(rest[numberEnd]) || rest[numberEnd] == '.') {
|
|
||||||
numberEnd++
|
|
||||||
}
|
|
||||||
unitEnd := numberEnd
|
|
||||||
for unitEnd < len(rest) && !isDigit(rest[unitEnd]) && rest[unitEnd] != '.' {
|
|
||||||
unitEnd++
|
|
||||||
}
|
|
||||||
if numberEnd == 0 || unitEnd == numberEnd {
|
|
||||||
return 0, fmt.Errorf("time: invalid duration '%s'", value)
|
|
||||||
}
|
|
||||||
number, unit := rest[:numberEnd], rest[numberEnd:unitEnd]
|
|
||||||
if unit == "d" {
|
|
||||||
// Use exact decimal arithmetic to avoid float rounding and retain nanosecond precision.
|
|
||||||
days, ok := new(big.Rat).SetString(number)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("time: invalid duration '%s'", value)
|
|
||||||
}
|
|
||||||
days.Mul(days, new(big.Rat).SetInt64(int64(24*time.Hour)))
|
|
||||||
nanoseconds := new(big.Int).Quo(days.Num(), days.Denom())
|
|
||||||
normalised.WriteString(nanoseconds.String())
|
|
||||||
normalised.WriteString("ns")
|
|
||||||
} else {
|
|
||||||
normalised.WriteString(rest[:unitEnd])
|
|
||||||
}
|
|
||||||
rest = rest[unitEnd:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The standard parser validates the remaining units and checks the total for overflow.
|
|
||||||
duration, err := time.ParseDuration(normalised.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("time: invalid duration '%s': %w", value, err)
|
|
||||||
}
|
|
||||||
return duration, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDigit(c byte) bool {
|
|
||||||
return c >= '0' && c <= '9'
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseDuration(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tt := range []struct {
|
|
||||||
input string
|
|
||||||
want time.Duration
|
|
||||||
}{
|
|
||||||
{"0", 0},
|
|
||||||
{"2m30s", 150 * time.Second},
|
|
||||||
{"1µs", time.Microsecond},
|
|
||||||
{"1μs", time.Microsecond},
|
|
||||||
{"2d", 48 * time.Hour},
|
|
||||||
{"010d", 240 * time.Hour},
|
|
||||||
{"08d", 192 * time.Hour},
|
|
||||||
{"2d3h", 51 * time.Hour},
|
|
||||||
{"3h2d", 51 * time.Hour},
|
|
||||||
{"1d1d", 48 * time.Hour},
|
|
||||||
{"1.5d", 36 * time.Hour},
|
|
||||||
{".5d", 12 * time.Hour},
|
|
||||||
{"1.d", 24 * time.Hour},
|
|
||||||
{"-2d3h", -51 * time.Hour},
|
|
||||||
{"+2d", 48 * time.Hour},
|
|
||||||
{"0d", 0},
|
|
||||||
{"0d1ns", time.Nanosecond},
|
|
||||||
{"0.000000000001d", 86 * time.Nanosecond},
|
|
||||||
{"-0.000000000001d", -86 * time.Nanosecond},
|
|
||||||
{"106751d23h47m16.854775807s", time.Duration(math.MaxInt64)},
|
|
||||||
{"-106751d23h47m16.854775808s", time.Duration(math.MinInt64)},
|
|
||||||
} {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
actual, err := ParseDuration(tt.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDuration_Invalid(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, input := range []string{
|
|
||||||
"", "d", "2d3", "1..2d", ".d", "1d-2h", "1d+2h", "1d 2h", "1day", "2D", "1w",
|
|
||||||
"1e2d", "1d2w", "106752d", "-106752d", "106751d23h47m16.854775808s",
|
|
||||||
"-106751d23h47m16.854775809s", "999999999999999999999999999d",
|
|
||||||
} {
|
|
||||||
t.Run(input, func(t *testing.T) {
|
|
||||||
_, err := ParseDuration(input)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/charmbracelet/colorprofile"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
@@ -25,10 +24,6 @@ type Formatter struct {
|
|||||||
maxServiceWidth int
|
maxServiceWidth int
|
||||||
|
|
||||||
utc bool
|
utc bool
|
||||||
|
|
||||||
// Cache each stream's terminal profile to avoid detection on every log entry.
|
|
||||||
stdout *colorprofile.Writer
|
|
||||||
stderr *colorprofile.Writer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFormatter(machineNames, serviceNames []string, utc bool) *Formatter {
|
func NewFormatter(machineNames, serviceNames []string, utc bool) *Formatter {
|
||||||
@@ -55,8 +50,6 @@ func NewFormatter(machineNames, serviceNames []string, utc bool) *Formatter {
|
|||||||
maxMachineWidth: maxMachineWidth,
|
maxMachineWidth: maxMachineWidth,
|
||||||
maxServiceWidth: maxServiceWidth,
|
maxServiceWidth: maxServiceWidth,
|
||||||
utc: utc,
|
utc: utc,
|
||||||
stdout: colorprofile.NewWriter(os.Stdout, os.Environ()),
|
|
||||||
stderr: colorprofile.NewWriter(os.Stderr, os.Environ()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +129,7 @@ func (f *Formatter) PrintEntry(entry api.ServiceLogEntry) {
|
|||||||
output.WriteString(f.formatMachine(entry.Metadata.MachineName))
|
output.WriteString(f.formatMachine(entry.Metadata.MachineName))
|
||||||
output.WriteString(" ")
|
output.WriteString(" ")
|
||||||
|
|
||||||
// Service/container_id or service name for a system service.
|
// Service/container_id or service name for a systemd service.
|
||||||
output.WriteString(f.formatService(entry.Metadata.ServiceName, entry.Metadata.ContainerID, entry.Metadata.Hook))
|
output.WriteString(f.formatService(entry.Metadata.ServiceName, entry.Metadata.ContainerID, entry.Metadata.Hook))
|
||||||
output.WriteString(" ")
|
output.WriteString(" ")
|
||||||
|
|
||||||
@@ -145,9 +138,9 @@ func (f *Formatter) PrintEntry(entry api.ServiceLogEntry) {
|
|||||||
|
|
||||||
// Print to appropriate stream.
|
// Print to appropriate stream.
|
||||||
if entry.Stream == api.LogStreamStderr {
|
if entry.Stream == api.LogStreamStderr {
|
||||||
fmt.Fprint(f.stderr, output.String())
|
fmt.Fprint(os.Stderr, output.String())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(f.stdout, output.String())
|
fmt.Print(output.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +148,8 @@ func (f *Formatter) PrintEntry(entry api.ServiceLogEntry) {
|
|||||||
func (f *Formatter) printError(entry api.ServiceLogEntry) {
|
func (f *Formatter) printError(entry api.ServiceLogEntry) {
|
||||||
if entry.Metadata.ServiceName == "" {
|
if entry.Metadata.ServiceName == "" {
|
||||||
msg := fmt.Sprintf("ERROR: %v", entry.Err)
|
msg := fmt.Sprintf("ERROR: %v", entry.Err)
|
||||||
fmt.Fprintln(f.stderr, tui.BoldRed.Render(msg))
|
style := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.BrightRed)
|
||||||
|
fmt.Fprintln(os.Stderr, style.Render(msg))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +160,7 @@ func (f *Formatter) printError(entry api.ServiceLogEntry) {
|
|||||||
stringid.TruncateID(entry.Metadata.ContainerID),
|
stringid.TruncateID(entry.Metadata.ContainerID),
|
||||||
entry.Metadata.MachineName)
|
entry.Metadata.MachineName)
|
||||||
} else {
|
} else {
|
||||||
msg = fmt.Sprintf("WARNING: log stream from system service '%s' on machine '%s'",
|
msg = fmt.Sprintf("WARNING: log stream from systemd service '%s' on machine '%s'",
|
||||||
entry.Metadata.ServiceName,
|
entry.Metadata.ServiceName,
|
||||||
entry.Metadata.MachineName)
|
entry.Metadata.MachineName)
|
||||||
}
|
}
|
||||||
@@ -177,7 +171,8 @@ func (f *Formatter) printError(entry api.ServiceLogEntry) {
|
|||||||
msg += fmt.Sprintf(": %v", entry.Err)
|
msg += fmt.Sprintf(": %v", entry.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(f.stderr, tui.BoldYellow.Render(msg))
|
style := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.BrightYellow)
|
||||||
|
fmt.Fprintln(os.Stderr, style.Render(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// palette is available colors for machine/service differentiation.
|
// palette is available colors for machine/service differentiation.
|
||||||
|
|||||||
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user