Compare commits

..
114 Commits
Author SHA1 Message Date
Pasha Sviderski db55df64a2 chore: bump caddy Go pkg dependency to v2.11.4 2026-09-14 19:38:49 +10:00
Pasha Sviderski e9e7b3a61e chore: move legacy unused WG tunnel and client connector to experiment/wg 2026-09-14 19:22:42 +10:00
Pasha Sviderski e81e130544 chore: go mod tidy 2026-09-12 07:48:18 +10:00
Pasha Sviderski 1e471d3ec4 feat(caddystorage): lint 2026-09-11 20:25:03 +10:00
Pasha Sviderski f7be7c55c9 feat(caddystorage): minor logging formatting 2026-09-11 20:17:41 +10:00
Pasha Sviderski 6ab02f95fe feat(caddystorage): simplify active locks tracking and cleanup 2026-09-11 20:14:35 +10:00
Pasha Sviderski 9a4098694c feat(caddystorage): init Caddy storage module backed by an Uncloud cluster 2026-09-09 20:11:09 +10:00
Pasha Sviderski 992b819f5b feat(caddystorage): simplify CaddyStorage gRPC API to allow only one2one calls 2026-09-08 14:36:43 +10:00
Pasha Sviderski 842f796e38 refactor(store): unify logic and enhance logging for initial store sync (waitStoreSync) 2026-09-08 12:04:26 +10:00
Pasha Sviderski 0f6c03240b feat(store): add WaitForStoreVersion API to wait for cluster store replication up to target version 2026-09-08 10:52:53 +10:00
Pasha Sviderski c6d0371f15 feat(caddystorage): integrate CaddyStorage gRPC service in machine API 2026-09-07 10:44:08 +10:00
Pasha Sviderski 5e01db5e20 fix(compose): remove unnecessary warning validating deploy.mode that is error 2026-09-04 11:00:05 +10:00
Pasha Sviderski 8aedc2bf19 refactor(proto): move protobuf generated API to the top-level api/pb package 2026-09-04 10:50:30 +10:00
Pasha Sviderski 7a88865f64 feat(caddystorage): implement the machine-local CaddyStorage gRPC service 2026-09-04 10:50:30 +10:00
Pasha Sviderski a24fc11caf feat(caddystorage): generic namespace-scoped key-value storage using cluster table in Corrosion 2026-09-04 10:50:30 +10:00
Pasha Sviderski c7e0e36daa feat(caddystorage): define CaddyStorage gRPC service API for distributed Caddy storage 2026-09-04 10:50:30 +10:00
Pasha Sviderski 01426ca03d feat(distlock): add smoke e2e test for distributed lock (lease) in cluster 2026-09-04 10:50:30 +10:00
Pasha Sviderski 3f86b3de44 feat(distlock): add integration tests for grpc transport 2026-09-04 10:50:30 +10:00
Miek GiebenandGitHub aaf67687d2 feat: validate remaining unsupported items of the support-matrix (#429)
* feat: validate remaining unsupported items of the support-matrix

Validate unsupported compose features: external configs, container_name,
and placement.

This makes the code match the support-matrix as of now (added comment to
that effect as well).

Signed-off-by: Miek Gieben <miek@miek.nl>

* support matrix: some deploy modes are supported

Add tests for the various non-supported deploy modes.
Prefix the warnings with `deploy ` to signal the section, and to
differentiate between top level 'labels' and 'deploy labels'.

Signed-off-by: Miek Gieben <miek@miek.nl>

---------

Signed-off-by: Miek Gieben <miek@miek.nl>
2026-09-04 10:40:03 +10:00
Pasha Sviderski 1c29d4d5d0 feat(distlock): integrate distributed lease management into Machine API 2026-08-28 20:59:10 +10:00
Pasha Sviderski 00d68d9465 refactor(client): make ProxySingleMachineContext and ProxyMachinesContext package functions as well 2026-08-28 20:37:50 +10:00
Pasha Sviderski 43bf2baaf7 feat(distlock): implement distributed Locker based on Redlock algorithm 2026-08-28 20:34:06 +10:00
Pasha Sviderski 613cd6e418 feat(distlock): refactor into a standalone package 2026-08-27 15:49:59 +10:00
Pasha Sviderski 7a7a313426 feat(distlock): add gRPC Lease server that adapts a machine-local Store 2026-08-27 12:27:54 +10:00
Pasha Sviderski a5ab6e3788 feat(distlock): implement in-memory lease store 2026-08-26 16:53:21 +10:00
Pasha Sviderski 5a956e72de feat(distlock): declare Lease gRPC service API for distributed locks 2026-08-26 16:06:47 +10:00
Felix HummelandGitHub ac56754281 docs(install): mise installation (#421) 2026-08-21 15:14:02 +10:00
b7e224a1ef feat(compose): support stdin_open and tty (#419)
* feat: support stdin_open and tty

This can be useful to leave a container running without specifying a
command like `sleep`, and probably in other situations as well.

Signed-off-by: Miek Gieben <miek@miek.nl>

* Complete full spec test case

Signed-off-by: Miek Gieben <miek@miek.nl>

* fix container log streaming for containers with TTY

---------

Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-07-30 21:50:54 +10:00
Pasha Sviderski 351698c280 fix(cli): completion with direct connections (--connect, --context, --uncloud-config) (fixes #377) 2026-07-21 17:01:21 +10:00
Pasha Sviderski fa77edf53e fix(proxy): don't shutdown 'uc proxy' when a client connection aborts 2026-07-21 14:16:46 +10:00
Pasha Sviderski b99abb7c39 website: update docs social card and favicons 2026-07-14 12:54:44 +10:00
Pasha Sviderski c2ae11a293 docs(cluster): add section about SSH user permissions when connection to the cluster 2026-07-14 12:54:44 +10:00
Pasha Sviderski f1555259de chore(docs): update command outputs in Deploy demo app doc, add section to view logs 2026-07-14 12:54:44 +10:00
Pasha Sviderski 50f8fbcfda fix(install): correctly compare installed nightly daemon version to upgrade to latest 2026-07-14 12:54:44 +10:00
Pasha Sviderski 73f29092ff docs: update use cases on the Overview page 2026-07-14 12:54:44 +10:00
Pasha Sviderski 08b24af341 website: split newsletter subscription into a separate section 2026-07-14 12:54:43 +10:00
Pasha Sviderski b9c54f1ff5 chore(website): configure ingress Caddy as trusted proxy to forward client IPs 2026-07-14 12:54:43 +10:00
Pasha Sviderski 4d76dd601c website: refine faq open source question, update og image 2026-07-14 12:54:43 +10:00
Dario GriffoandGitHub 3af9936d27 docs(install): update Debian repository domain (#413)
The community repository moved from debian.griffo.io to deb.griffo.io
(Debian trademark policy; old domain redirects permanently). Also use
the keyring + signed-by layout and add the missing sudo and apt update.
2026-07-14 12:54:29 +10:00
Pasha Sviderski dc721e511e fix(daemon): extend systemd service start timeout when pulling Docker image for corrosion service 2026-07-10 20:42:27 +10:00
Pasha Sviderski 054b3b8fd4 website: change CTA link to getting started docs 2026-07-09 19:07:25 +10:00
Pasha Sviderski b3896ff642 website: minor 2026-07-09 18:56:46 +10:00
Pasha Sviderski 5b2823de1e website: update meta/og tags and images 2026-07-09 18:56:46 +10:00
Pasha Sviderski 0007f76d09 website: serve hub as /hub 2026-07-09 18:56:46 +10:00
Pasha Sviderski f8e6d1caaa website: send Hub early access form submission to posthog 2026-07-09 18:56:46 +10:00
Pasha Sviderski e684795aee website: refine styles for index and hub 2026-07-09 18:56:46 +10:00
Pasha Sviderski 7964273552 chore(experiments): go mod tidy 2026-07-09 18:56:46 +10:00
Pasha Sviderski 4ad4bce29d website: refine FAQ and form on hub page 2026-07-09 18:56:46 +10:00
Pasha Sviderski d670d18810 website: add new Hub page 2026-07-09 18:56:46 +10:00
Pasha Sviderski a1957a156e docs(install): update machine init/add output that doesn't install corrosion systemd service 2026-07-09 18:56:45 +10:00
JiraliteandGitHub 6266e9ec3a chore(nightly-build): prettier warning in description (#410) 2026-07-06 12:07:59 +10:00
Anton OvchinnikovandGitHub 7c4fcde88d feat(cli): support env var UNCLOUD_DAEMON_VERSION to specify daemon version for "uc machine add/init" (#409) 2026-07-06 10:47:56 +10:00
Nick CampbellandGitHub d36b28c55a fix(docs): version command instead of --version in docs (#408) 2026-07-05 07:17:34 +10:00
Miek GiebenandGitHub 6b41340f1d chore(cli): use cli.Cancelled when cancellikng the command to return non-zero exit code in all cases (#406)
Use `cli.Cancelled` in all cases where we cancel an operation instead of Printf && return nil

Signed-off-by: Miek Gieben <miek@miek.nl>
2026-06-30 19:49:11 +10:00
Pasha Sviderski 4b9ea43402 docs(cli): update machine init/add help with info about installing Docker and uncloudd 2026-06-29 21:03:11 +10:00
Pasha Sviderski e5f23a9189 fix(install): allow to install uc CLI version <0.20 after the artifact renaming 2026-06-26 21:39:39 +10:00
Pasha Sviderski 15e3e32b47 chore(grpc): update min client/server versions to 0.20.0 2026-06-26 20:53:42 +10:00
Pasha Sviderski a5d73fe4d7 refactor(cli): move cmd/uncloud to cmd/uc, change the artifact name uncloud_* -> uc_* 2026-06-26 20:07:15 +10:00
Pasha Sviderski e01c8d3cc7 docs(secrets): add new Concepts/Secrets doc about passing sensitive data to services, including x-command extension 2026-06-26 19:36:42 +10:00
Pasha SviderskiandGitHub 925695a44a feat(secrets): initial support for 'x-command' and 'driver: exec' secrets, referenced as secret://name in environment (#403) 2026-06-26 12:00:16 +10:00
Pasha Sviderski e5b205391e fix(install): install curl on machine init/add if missing (fixes #402) 2026-06-25 10:52:22 +10:00
Pasha Sviderski e9762dda21 chore(api): add TODO to refactor Machine* domain types in api/client packages 2026-06-25 07:13:47 +10:00
Pasha Sviderski 5bfdbd5c8f docs: regenerate CLI reference 2026-06-24 20:09:15 +10:00
Pasha Sviderski 1d5c71730f feat(cli): add JSON output format for 'uc machine ls', remove MACHINE ID column from table output 2026-06-24 20:02:56 +10:00
Pasha Sviderski ad43569fe5 feat(machine): add OS and kernel version information to machine info, show in 'machine ls' 2026-06-24 17:57:20 +10:00
Pasha Sviderski 9ea30e6ee9 refactor(corrosion): reduce transport retry time for corrosion 10s->2s, move some logs to error level 2026-06-24 16:06:00 +10:00
Pasha Sviderski a040ce5f8e chore: make retry log debug 2026-06-24 14:50:45 +10:00
Pasha Sviderski 328d17599b fix(machine): improve context cancellation handling during watch and sync operations 2026-06-24 13:41:14 +10:00
Pasha Sviderski 83256b5bf7 fix(corrosion): handle 404 errors during resubscription and stop retrying 2026-06-24 12:03:45 +10:00
Pasha Sviderski 35191c00e0 fix(cluster): improve error handling on failed machine subscription in cluster store 2026-06-23 20:54:25 +10:00
Pasha Sviderski b08cee03e3 refactor(machine): improve startup and shuitdown logic, shutdown corrosion in one place 2026-06-23 16:03:04 +10:00
Pasha Sviderski 99e1ae0edb fix(corrosion): handle resubscription with from_change=0 that streams rows in new corrosion version 2026-06-23 09:34:45 +10:00
Pasha Sviderski 5f07bc5832 fix(machine): correctly recreate machine in store if missing, improve context handling 2026-06-23 09:29:04 +10:00
Pasha Sviderski 8aa0a96c23 feat(machine ls): add ARCH, VERSION, DOCKER columns in the output table 2026-06-22 19:49:11 +10:00
Pasha Sviderski 8b95db66d1 feat(machine): sync machine arch, daemon and docker versions to store, sync machine info on schedule 2026-06-22 19:47:35 +10:00
Pasha Sviderski 69acf6f58e docs: regenerate CLI reference 2026-06-19 20:55:48 +10:00
Pasha Sviderski 18b4112a33 ucind: use corrosion image tag 2026.6.15 2026-06-19 20:51:59 +10:00
Pasha Sviderski 23d6b4409a machine: make each machine the source of truth for its configuration, sync it to cluster store 2026-06-19 20:51:19 +10:00
Pasha Sviderski 0c42a7df65 cluster: derive default machine name from its hostname when adding to cluster 2026-06-19 16:54:59 +10:00
Pasha Sviderski e3823c29bf cli: strip ANSI sequences (colors, styles) from table output when stdout is not a TTY 2026-06-18 20:29:22 +10:00
Pasha Sviderski 2f717a10a7 cli: do not use interactive TUI elements when control terminal (tty) is not available (fixes #386) 2026-06-18 18:22:48 +10:00
Pasha Sviderski 7bb3cb9682 logs: stream corrosion logs with 'uc machine logs corrosion' from container instead of journal (fixes #392) 2026-06-17 18:28:25 +10:00
Pasha Sviderski 247154eeb3 cli: use forked bubbletea with a fix to drain pending TTY input on shutdown (#286) 2026-06-17 16:32:09 +10:00
Pasha Sviderski 7bac40ae3e client: refactor ListImages to return an error per machine 2026-06-17 16:32:09 +10:00
Pasha Sviderski d36ff67409 unregistry: change the listening port on each machine 5000 -> 51500 2026-06-17 16:32:09 +10:00
Pasha Sviderski 3841da9e1d website: node instead of machine in header, cut subheader 2026-06-17 16:32:09 +10:00
cffa03007b volumes: error when using relative volumes sources in compose (#353)
Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-06-17 13:54:43 +10:00
Miek GiebenandGitHub 73d515bb40 cli: add -m/--machine flag completion for 'uc machine logs' (#352)
Signed-off-by: Miek Gieben <miek@miek.nl>
2026-06-17 13:18:53 +10:00
Pasha Sviderski 0c3b8b122f network: add auto-detection of optimal MTU for WireGuard interface, --wg-mtu flag, set max_mtu for Corrosion to 1232 2026-06-16 21:36:12 +10:00
Pasha Sviderski 2f76187bf5 corrosion: use Corrosion 2026.6.15 with fixed API authz for CLI query/exec commands 2026-06-15 16:34:06 +10:00
Pasha Sviderski f275a31096 website: include js in the image 2026-06-12 13:41:32 +10:00
Pasha Sviderski e622c06ca6 website: landing update: Multi-machine Docker Compose for production with terminal examples 2026-06-11 19:07:16 +10:00
Pasha Sviderski fffc6a8b8a website: landing revamp with deploy demo and testimonial 2026-06-10 19:53:28 +10:00
Miek GiebenandGitHub cccb8635e1 ucind: add command aliases rm/delete (#396)
Signed-off-by: Miek Gieben <miek@miek.nl>
2026-06-08 16:34:42 +10:00
1247f961c2 feat: allow to bind to multiple host IP addresses specified as CIDR prefix in x-ports (#358)
Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-06-04 20:07:58 +10:00
3586a32987 feat: add 'uc proxy' command to proxy a service port to a local port (#384)
Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-06-02 18:56:16 +10:00
24b81af43c feat: add :51090/metrics endpoint with Prometheus metrics listening on machine IP (#304)
Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-05-29 18:23:07 +10:00
Miek GiebenandGitHub 11df8e4a75 feat: add 'uc caddy logs' alias for 'uc logs caddy' (#381) 2026-05-28 19:45:39 +10:00
Pasha Sviderski 9424daff28 feat: enable auth for local requests to Corrosion API (resolves #110) 2026-05-26 10:07:12 +10:00
Pasha Sviderski 26951773d1 feat: add created_at/updated_at columns to cluster and machines store tables 2026-05-26 10:07:12 +10:00
Immanuel TikhonovandGitHub 7616f1365a fix: reject extra args in context commands (#376) 2026-05-26 09:22:50 +10:00
Pasha Sviderski 2aaaa70b15 chore: mod tidy 2026-05-25 21:49:04 +10:00
Pasha Sviderski 442b5c62bf refactor: cleanup Corrosion container on reset, move admin socket to runtime dir 2026-05-25 21:41:35 +10:00
Pasha Sviderski 71da32549a refactor: migrate to run Corrosion service as managed container instead of systemd unit, automatically migrate data to v1 2026-05-25 17:15:44 +10:00
Pasha Sviderski 9236aa341a refactor: migrate to latest Corrosion version 2026.5.14 for ucind (compare versions vector for store sync) 2026-05-25 14:09:51 +10:00
Pasha Sviderski 0dd4bc9d36 chore: build a use new corrosion image 2026.5.14 (pointing to v1.0.0 upstream) for ucind 2026-05-22 19:36:07 +10:00
Pasha Sviderski 07acb31bf3 fix: do not include outdated containers from the removed machine in DNS and Caddy configs 2026-05-21 19:33:28 +10:00
Pasha Sviderski a26af3b2ae feat: make machine init/add upgrade installed daemon binary to latest or specified version 2026-05-21 15:58:04 +10:00
Pasha Sviderski 29faeef5a5 docs: regenerate CLI reference 2026-05-21 15:17:49 +10:00
Pasha Sviderski 2f159157cc feat(version): replace --version with 'version' command to print build information for uc and uncloudd 2026-05-21 15:14:08 +10:00
Pasha Sviderski 28c88e4e10 feat(version): collect build-time metadata for uncloud binaries in version pkg 2026-05-21 15:14:08 +10:00
Pasha Sviderski 7f7d091429 chore: make the reset machine prompt a bit clearer 2026-05-21 15:14:08 +10:00
Aaron EcholsandGitHub 424263bdd4 feat: allow to customise WireGuard listen port with --wg-port for machine init/add (#366) 2026-05-19 16:55:55 +10:00
c95136eae6 BREAKING CHANGE: resolve gRPC API proxy targets (machines) on server instead of client, needs upgrade to v0.20 (#247)
Co-authored-by: Pasha Sviderski <me@psviderski.name>
2026-05-19 15:22:50 +10:00
281 changed files with 19825 additions and 8414 deletions
+1
View File
@@ -2,6 +2,7 @@
*
# Allow files and directories.
!api/
!cmd/
!internal/
!pkg/
+2 -1
View File
@@ -57,7 +57,8 @@ jobs:
The commands below show how to install the nightly version of uncloud.
> **Warning**: These are nightly development builds and may be unstable.
> [!WARNING]
> These are nightly development builds and may be unstable.
**Install uncloud CLI:**
+19 -10
View File
@@ -20,9 +20,9 @@ snapshot:
version_template: "{{ incminor .Version }}-nightly-{{.ShortCommit}}"
builds:
- id: uncloud
main: ./cmd/uncloud
binary: uncloud
- id: uc
main: ./cmd/uc
binary: uc
env:
# Use CGO to be able to build fsevent required by compose on macOS.
- CGO_ENABLED={{ if eq .Os "darwin" }}1{{ else }}0{{ end }}
@@ -34,7 +34,12 @@ builds:
- amd64
- arm64
ldflags:
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
- -s -w
-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
main: ./cmd/uncloudd
@@ -47,12 +52,17 @@ builds:
- amd64
- arm64
ldflags:
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
- -s -w
-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:
- id: uncloud
- id: uc
builds:
- uncloud
- uc
formats:
# gz is not compatible with homebrew releases, so we use tar.gz
- tar.gz
@@ -93,10 +103,9 @@ brews:
description: "Uncloud CLI"
homepage: "https://uncloud.run"
ids:
- uncloud
- uc
install: |
bin.install "uncloud"
bin.install_symlink "uncloud" => "uc"
bin.install "uc"
skip_upload: false
repository:
owner: psviderski
+4 -4
View File
@@ -72,7 +72,7 @@ management.
- **`cmd/`**: Contains main applications
- `uncloud/`: CLI tool with subcommands for machine, service, volume management
- `uc/`: CLI tool with subcommands for machine, service, volume management
- `uncloudd/`: Daemon that runs on each machine
- `ucind/`: Development cluster management for testing
@@ -131,7 +131,7 @@ github.com/siderolabs/grpc-proxy // gRPC proxy for forwarding
```bash
# Build binaries
go build -o uncloud ./cmd/uncloud
go build -o uc ./cmd/uc
go build -o uncloudd ./cmd/uncloudd
```
@@ -188,7 +188,7 @@ uc context use <name> # Switch context
- **Package naming**: Use clear, descriptive names
- **Error handling**: Wrap errors with context using `fmt.Errorf`
- **Logging**: Use structured logging with levels
- **gRPC**: Services defined in `internal/machine/api/pb/`
- **gRPC**: Services defined in `api/pb/`
### Testing
@@ -239,7 +239,7 @@ uc context use <name> # Switch context
### Important Files to Understand
- `cmd/uncloud/main.go`: CLI entry point and command structure
- `cmd/uc/main.go`: CLI entry point and command structure
- `internal/cli/cli.go`: CLI implementation and configuration
- `internal/machine/machine.go`: Core machine management
- `pkg/api/`: Public API definitions
+9 -5
View File
@@ -18,6 +18,7 @@ RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o uncloudd ./cmd/uncloudd
FROM alpine:${ALPINE_VERSION} AS corrosion-download
ARG TARGETARCH
ARG CORROSION_VERSION
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
"amd64") echo "x86_64" ;; \
@@ -25,20 +26,21 @@ RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
esac) \
&& wget -q -O /tmp/corrosion.tar.gz \
"https://github.com/psviderski/corrosion/releases/latest/download/corrosion-${CORROSION_ARCH}-unknown-linux-gnu.tar.gz" \
"https://github.com/psviderski/corrosion/releases/download/v${CORROSION_VERSION}/corrosion-${CORROSION_ARCH}-unknown-linux-gnu.tar.gz" \
&& tar -xzf /tmp/corrosion.tar.gz -C /tmp \
&& install /tmp/corrosion /usr/local/bin/corrosion \
&& install /tmp/corrosion /usr/bin/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,
# 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-debian12:latest AS corrosion
COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion
FROM gcr.io/distroless/cc-debian13:latest AS corrosion
COPY --from=corrosion-download /usr/bin/corrosion /usr/bin/corrosion
CMD ["corrosion", "agent"]
FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball
ARG CORROSION_IMAGE="ghcr.io/psviderski/corrosion:latest"
ARG CORROSION_VERSION
ARG CORROSION_IMAGE="ghcr.io/unlabs-dev/corrosion:${CORROSION_VERSION}"
ARG TARGETOS
ARG TARGETARCH
@@ -47,6 +49,8 @@ RUN crane pull --platform ${TARGETOS}/${TARGETARCH} "${CORROSION_IMAGE}" /corros
# Uncloud-in-Docker (ucind) image for running Uncloud test clusters using Docker.
FROM docker:29.4.0-dind AS ucind
ARG CORROSION_VERSION
# Create system group and user 'uncloud'.
RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud
RUN apk --no-cache add \
+3 -3
View File
@@ -32,13 +32,13 @@ available outside the project, so they don't interfere with system packages.
Build the CLI:
```shell
go build -o uc ./cmd/uncloud
go build -o uc ./cmd/uc
```
Or build and run the CLI with a single command:
```shell
go run ./cmd/uncloud --help
go run ./cmd/uc --help
```
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
Update the generated Go code for the machine gRPC API after modifying `.proto` files:
Update the generated Go code for the gRPC API after modifying files in [`api/pb`](./api/pb):
```shell
mise run proto
+6 -4
View File
@@ -1,6 +1,7 @@
# TODO: Makefile is deprecated, add new targets as mise tasks in mise.toml instead.
CORROSION_IMAGE ?= ghcr.io/psviderski/corrosion:latest
CORROSION_VERSION ?= 2026.6.15
CORROSION_IMAGE = ghcr.io/unlabs-dev/corrosion:$(CORROSION_VERSION)
UCIND_IMAGE ?= ghcr.io/psviderski/ucind:latest
.PHONY: ucind-cluster
@@ -9,11 +10,12 @@ ucind-cluster:
.PHONY: corrosion-image
corrosion-image:
docker build -t "$(CORROSION_IMAGE)" --target corrosion .
docker build --build-arg CORROSION_VERSION=$(CORROSION_VERSION) -t "$(CORROSION_IMAGE)" --target corrosion .
.PHONY: corrosion-multiarch-image-push
corrosion-multiarch-image-push:
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(CORROSION_IMAGE)" --target corrosion .
docker buildx build --build-arg CORROSION_VERSION=$(CORROSION_VERSION) --push --platform linux/amd64,linux/arm64 \
-t "$(CORROSION_IMAGE)" --target corrosion .
.PHONY: ucind-multiarch-image-push
ucind-multiarch-image-push:
@@ -70,4 +72,4 @@ _lint:
.PHONY: cli-docs
cli-docs:
go run ./cmd/uncloud docs
go run ./cmd/uc docs
+8 -15
View File
@@ -154,7 +154,6 @@ Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
```bash
$ 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...
✓ Docker is already installed.
⏳ Installing Docker...
@@ -169,9 +168,6 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
✓ Systemd unit file created: /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)...
✓ Uncloud machine daemon started.
✓ Uncloud installed on the machine successfully! 🎉
@@ -190,21 +186,20 @@ DNS records updated to use only the internet-reachable machines running caddy se
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
```
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.
1. The CLI SSHs into the machine and installs Docker and the `uncloudd` machine daemon, managed by systemd.
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`
through its gRPC API over SSH.
3. Configures and starts `corrosion`, a CRDT-based distributed SQLite database to share cluster state between machines.
3. Configures and starts [corrosion](https://github.com/superfly/corrosion), a CRDT-based distributed SQLite database to
share cluster state between machines, as a Docker container managed by `uncloudd`.
4. Creates a Docker bridge network connected to the WireGuard interface.
5. This machine becomes an entry point for the newly created cluster which is stored in the cluster config under
`~/.config/uncloud` on your local machine.
5. This machine becomes an entry point for the newly created cluster. Its address is stored as a connection info in the
cluster config at `~/.config/uncloud/config.yaml` on your local machine.
**When you add another machine:**
```bash
$ 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...
✓ Docker is already installed.
✓ Linux user and group 'uncloud' created.
@@ -215,9 +210,6 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
✓ Systemd unit file created: /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)...
✓ Uncloud machine daemon started.
✓ Uncloud installed on the machine successfully! 🎉
@@ -246,9 +238,10 @@ 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.
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
network to communicate directly across machines.
5. Configures and starts `corrosion` on the second machine to sync the cluster state.
5. Configures and starts the `uncloud-corrosion` container on the second machine to sync the cluster state.
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 serve cluster operations.
7. If one of the machines goes offline, the other machine can still be used by `uc` to connect and run cluster
operations.
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
+174
View File
@@ -0,0 +1,174 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.27.3
// source: api/pb/caddy.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 GetCaddyConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The generated Caddyfile content.
Caddyfile string `protobuf:"bytes,1,opt,name=caddyfile,proto3" json:"caddyfile,omitempty"`
// Timestamp when the config was last modified.
ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"`
}
func (x *GetCaddyConfigResponse) Reset() {
*x = GetCaddyConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_caddy_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetCaddyConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetCaddyConfigResponse) ProtoMessage() {}
func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_caddy_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 GetCaddyConfigResponse.ProtoReflect.Descriptor instead.
func (*GetCaddyConfigResponse) Descriptor() ([]byte, []int) {
return file_api_pb_caddy_proto_rawDescGZIP(), []int{0}
}
func (x *GetCaddyConfigResponse) GetCaddyfile() string {
if x != nil {
return x.Caddyfile
}
return ""
}
func (x *GetCaddyConfigResponse) GetModifiedAt() *timestamppb.Timestamp {
if x != nil {
return x.ModifiedAt
}
return nil
}
var File_api_pb_caddy_proto protoreflect.FileDescriptor
var file_api_pb_caddy_proto_rawDesc = []byte{
0x0a, 0x12, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 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, 0x73, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x61,
0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x64, 0x64, 0x79, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x64, 0x64, 0x79, 0x66, 0x69, 0x6c, 0x65, 0x12,
0x3b, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 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, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x32, 0x49, 0x0a, 0x05,
0x43, 0x61, 0x64, 0x64, 0x79, 0x12, 0x40, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 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, 0x1b, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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_proto_rawDescOnce sync.Once
file_api_pb_caddy_proto_rawDescData = file_api_pb_caddy_proto_rawDesc
)
func file_api_pb_caddy_proto_rawDescGZIP() []byte {
file_api_pb_caddy_proto_rawDescOnce.Do(func() {
file_api_pb_caddy_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_proto_rawDescData)
})
return file_api_pb_caddy_proto_rawDescData
}
var file_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_api_pb_caddy_proto_goTypes = []any{
(*GetCaddyConfigResponse)(nil), // 0: api.GetCaddyConfigResponse
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
}
var file_api_pb_caddy_proto_depIdxs = []int32{
1, // 0: api.GetCaddyConfigResponse.modified_at:type_name -> google.protobuf.Timestamp
2, // 1: api.Caddy.GetConfig:input_type -> google.protobuf.Empty
0, // 2: api.Caddy.GetConfig:output_type -> api.GetCaddyConfigResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_api_pb_caddy_proto_init() }
func file_api_pb_caddy_proto_init() {
if File_api_pb_caddy_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*GetCaddyConfigResponse); 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_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_api_pb_caddy_proto_goTypes,
DependencyIndexes: file_api_pb_caddy_proto_depIdxs,
MessageInfos: file_api_pb_caddy_proto_msgTypes,
}.Build()
File_api_pb_caddy_proto = out.File
file_api_pb_caddy_proto_rawDesc = nil
file_api_pb_caddy_proto_goTypes = nil
file_api_pb_caddy_proto_depIdxs = nil
}
@@ -2,7 +2,7 @@ syntax = "proto3";
package api;
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
option go_package = "github.com/psviderski/uncloud/api/pb";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
@@ -17,4 +17,4 @@ message GetCaddyConfigResponse {
string caddyfile = 1;
// Timestamp when the config was last modified.
google.protobuf.Timestamp modified_at = 2;
}
}
@@ -2,7 +2,7 @@
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: internal/machine/api/pb/caddy.proto
// source: api/pb/caddy.proto
package pb
@@ -120,5 +120,5 @@ var Caddy_ServiceDesc = grpc.ServiceDesc{
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/machine/api/pb/caddy.proto",
Metadata: "api/pb/caddy.proto",
}
+689
View File
@@ -0,0 +1,689 @@
// 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
}
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, 0x32, 0xdf, 0x02, 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, 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, 8)
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
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 9: google.protobuf.Empty
}
var file_api_pb_caddy_storage_proto_depIdxs = []int32{
8, // 0: api.LoadCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
8, // 1: api.StatCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
0, // 2: api.CaddyStorage.Store:input_type -> api.StoreCaddyStorageRequest
1, // 3: api.CaddyStorage.Load:input_type -> api.LoadCaddyStorageRequest
3, // 4: api.CaddyStorage.Delete:input_type -> api.DeleteCaddyStorageRequest
4, // 5: api.CaddyStorage.List:input_type -> api.ListCaddyStorageRequest
6, // 6: api.CaddyStorage.Stat:input_type -> api.StatCaddyStorageRequest
9, // 7: api.CaddyStorage.Store:output_type -> google.protobuf.Empty
2, // 8: api.CaddyStorage.Load:output_type -> api.LoadCaddyStorageResponse
9, // 9: api.CaddyStorage.Delete:output_type -> google.protobuf.Empty
5, // 10: api.CaddyStorage.List:output_type -> api.ListCaddyStorageResponse
7, // 11: api.CaddyStorage.Stat:output_type -> api.StatCaddyStorageResponse
7, // [7:12] is the sub-list for method output_type
2, // [2:7] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] 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
}
}
}
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: 8,
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
}
+59
View File
@@ -0,0 +1,59 @@
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);
}
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;
}
+286
View File
@@ -0,0 +1,286 @@
// 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"
)
// 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)
}
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
}
// 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)
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) 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)
}
// 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,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/pb/caddy_storage.proto",
}
+949
View File
@@ -0,0 +1,949 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.27.3
// source: api/pb/cluster.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"
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 MachineMember_MembershipState int32
const (
MachineMember_UNKNOWN MachineMember_MembershipState = 0
// The member is active.
MachineMember_UP MachineMember_MembershipState = 1
// The member is active, but at least one cluster member suspects its down. For all purposes,
// a SUSPECT member is treated as if it were UP until either it refutes the suspicion (becoming UP)
// or fails to do so (being declared DOWN).
MachineMember_SUSPECT MachineMember_MembershipState = 2
// The member is confirmed DOWN.
MachineMember_DOWN MachineMember_MembershipState = 3
)
// Enum value maps for MachineMember_MembershipState.
var (
MachineMember_MembershipState_name = map[int32]string{
0: "UNKNOWN",
1: "UP",
2: "SUSPECT",
3: "DOWN",
}
MachineMember_MembershipState_value = map[string]int32{
"UNKNOWN": 0,
"UP": 1,
"SUSPECT": 2,
"DOWN": 3,
}
)
func (x MachineMember_MembershipState) Enum() *MachineMember_MembershipState {
p := new(MachineMember_MembershipState)
*p = x
return p
}
func (x MachineMember_MembershipState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MachineMember_MembershipState) Descriptor() protoreflect.EnumDescriptor {
return file_api_pb_cluster_proto_enumTypes[0].Descriptor()
}
func (MachineMember_MembershipState) Type() protoreflect.EnumType {
return &file_api_pb_cluster_proto_enumTypes[0]
}
func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MachineMember_MembershipState.Descriptor instead.
func (MachineMember_MembershipState) EnumDescriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2, 0}
}
type DNSRecord_RecordType int32
const (
DNSRecord_UNSPECIFIED DNSRecord_RecordType = 0
DNSRecord_A DNSRecord_RecordType = 1
DNSRecord_AAAA DNSRecord_RecordType = 2
)
// Enum value maps for DNSRecord_RecordType.
var (
DNSRecord_RecordType_name = map[int32]string{
0: "UNSPECIFIED",
1: "A",
2: "AAAA",
}
DNSRecord_RecordType_value = map[string]int32{
"UNSPECIFIED": 0,
"A": 1,
"AAAA": 2,
}
)
func (x DNSRecord_RecordType) Enum() *DNSRecord_RecordType {
p := new(DNSRecord_RecordType)
*p = x
return p
}
func (x DNSRecord_RecordType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (DNSRecord_RecordType) Descriptor() protoreflect.EnumDescriptor {
return file_api_pb_cluster_proto_enumTypes[1].Descriptor()
}
func (DNSRecord_RecordType) Type() protoreflect.EnumType {
return &file_api_pb_cluster_proto_enumTypes[1]
}
func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use DNSRecord_RecordType.Descriptor instead.
func (DNSRecord_RecordType) EnumDescriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{9, 0}
}
type AddMachineRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Network *NetworkConfig `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
PublicIp *IP `protobuf:"bytes,3,opt,name=public_ip,json=publicIp,proto3" json:"public_ip,omitempty"`
}
func (x *AddMachineRequest) Reset() {
*x = AddMachineRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddMachineRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddMachineRequest) ProtoMessage() {}
func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 AddMachineRequest.ProtoReflect.Descriptor instead.
func (*AddMachineRequest) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{0}
}
func (x *AddMachineRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *AddMachineRequest) GetNetwork() *NetworkConfig {
if x != nil {
return x.Network
}
return nil
}
func (x *AddMachineRequest) GetPublicIp() *IP {
if x != nil {
return x.PublicIp
}
return nil
}
type AddMachineResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Machine *MachineInfo `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"`
}
func (x *AddMachineResponse) Reset() {
*x = AddMachineResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddMachineResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddMachineResponse) ProtoMessage() {}
func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 AddMachineResponse.ProtoReflect.Descriptor instead.
func (*AddMachineResponse) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{1}
}
func (x *AddMachineResponse) GetMachine() *MachineInfo {
if x != nil {
return x.Machine
}
return nil
}
type MachineMember struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Machine *MachineInfo `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"`
State MachineMember_MembershipState `protobuf:"varint,2,opt,name=state,proto3,enum=api.MachineMember_MembershipState" json:"state,omitempty"`
}
func (x *MachineMember) Reset() {
*x = MachineMember{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MachineMember) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MachineMember) ProtoMessage() {}
func (x *MachineMember) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 MachineMember.ProtoReflect.Descriptor instead.
func (*MachineMember) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2}
}
func (x *MachineMember) GetMachine() *MachineInfo {
if x != nil {
return x.Machine
}
return nil
}
func (x *MachineMember) GetState() MachineMember_MembershipState {
if x != nil {
return x.State
}
return MachineMember_UNKNOWN
}
type ListMachinesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Machines []*MachineMember `protobuf:"bytes,1,rep,name=machines,proto3" json:"machines,omitempty"`
}
func (x *ListMachinesResponse) Reset() {
*x = ListMachinesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListMachinesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListMachinesResponse) ProtoMessage() {}
func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 ListMachinesResponse.ProtoReflect.Descriptor instead.
func (*ListMachinesResponse) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{3}
}
func (x *ListMachinesResponse) GetMachines() []*MachineMember {
if x != nil {
return x.Machines
}
return nil
}
type RemoveMachineRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *RemoveMachineRequest) Reset() {
*x = RemoveMachineRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RemoveMachineRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveMachineRequest) ProtoMessage() {}
func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 RemoveMachineRequest.ProtoReflect.Descriptor instead.
func (*RemoveMachineRequest) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{4}
}
func (x *RemoveMachineRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type Domain struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *Domain) Reset() {
*x = Domain{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Domain) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Domain) ProtoMessage() {}
func (x *Domain) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 Domain.ProtoReflect.Descriptor instead.
func (*Domain) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{5}
}
func (x *Domain) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type ReserveDomainRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
}
func (x *ReserveDomainRequest) Reset() {
*x = ReserveDomainRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReserveDomainRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReserveDomainRequest) ProtoMessage() {}
func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 ReserveDomainRequest.ProtoReflect.Descriptor instead.
func (*ReserveDomainRequest) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{6}
}
func (x *ReserveDomainRequest) GetEndpoint() string {
if x != nil {
return x.Endpoint
}
return ""
}
type CreateDomainRecordsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Records []*DNSRecord `protobuf:"bytes,1,rep,name=records,proto3" json:"records,omitempty"`
}
func (x *CreateDomainRecordsRequest) Reset() {
*x = CreateDomainRecordsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateDomainRecordsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateDomainRecordsRequest) ProtoMessage() {}
func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 CreateDomainRecordsRequest.ProtoReflect.Descriptor instead.
func (*CreateDomainRecordsRequest) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{7}
}
func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord {
if x != nil {
return x.Records
}
return nil
}
type CreateDomainRecordsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Records []*DNSRecord `protobuf:"bytes,1,rep,name=records,proto3" json:"records,omitempty"`
}
func (x *CreateDomainRecordsResponse) Reset() {
*x = CreateDomainRecordsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateDomainRecordsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateDomainRecordsResponse) ProtoMessage() {}
func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 CreateDomainRecordsResponse.ProtoReflect.Descriptor instead.
func (*CreateDomainRecordsResponse) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{8}
}
func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord {
if x != nil {
return x.Records
}
return nil
}
type DNSRecord struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type DNSRecord_RecordType `protobuf:"varint,2,opt,name=type,proto3,enum=api.DNSRecord_RecordType" json:"type,omitempty"`
Values []string `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"`
}
func (x *DNSRecord) Reset() {
*x = DNSRecord{}
if protoimpl.UnsafeEnabled {
mi := &file_api_pb_cluster_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DNSRecord) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DNSRecord) ProtoMessage() {}
func (x *DNSRecord) ProtoReflect() protoreflect.Message {
mi := &file_api_pb_cluster_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 DNSRecord.ProtoReflect.Descriptor instead.
func (*DNSRecord) Descriptor() ([]byte, []int) {
return file_api_pb_cluster_proto_rawDescGZIP(), []int{9}
}
func (x *DNSRecord) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *DNSRecord) GetType() DNSRecord_RecordType {
if x != nil {
return x.Type
}
return DNSRecord_UNSPECIFIED
}
func (x *DNSRecord) GetValues() []string {
if x != nil {
return x.Values
}
return nil
}
var File_api_pb_cluster_proto protoreflect.FileDescriptor
var file_api_pb_cluster_proto_rawDesc = []byte{
0x0a, 0x14, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
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, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62,
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x61,
0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70,
0x22, 0x40, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x22, 0xb4, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65,
0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d,
0x62, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74,
0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a, 0x0f, 0x4d, 0x65,
0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a,
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x55, 0x50,
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x53, 0x50, 0x45, 0x43, 0x54, 0x10, 0x02, 0x12,
0x08, 0x0a, 0x04, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73,
0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x73, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x06, 0x44, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x1a, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a,
0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54,
0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41,
0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0xca, 0x03, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65,
0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12,
0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64,
0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 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, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63,
0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 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, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 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, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 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, 0x0b, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12,
0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 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_cluster_proto_rawDescOnce sync.Once
file_api_pb_cluster_proto_rawDescData = file_api_pb_cluster_proto_rawDesc
)
func file_api_pb_cluster_proto_rawDescGZIP() []byte {
file_api_pb_cluster_proto_rawDescOnce.Do(func() {
file_api_pb_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_cluster_proto_rawDescData)
})
return file_api_pb_cluster_proto_rawDescData
}
var file_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_api_pb_cluster_proto_goTypes = []any{
(MachineMember_MembershipState)(0), // 0: api.MachineMember.MembershipState
(DNSRecord_RecordType)(0), // 1: api.DNSRecord.RecordType
(*AddMachineRequest)(nil), // 2: api.AddMachineRequest
(*AddMachineResponse)(nil), // 3: api.AddMachineResponse
(*MachineMember)(nil), // 4: api.MachineMember
(*ListMachinesResponse)(nil), // 5: api.ListMachinesResponse
(*RemoveMachineRequest)(nil), // 6: api.RemoveMachineRequest
(*Domain)(nil), // 7: api.Domain
(*ReserveDomainRequest)(nil), // 8: api.ReserveDomainRequest
(*CreateDomainRecordsRequest)(nil), // 9: api.CreateDomainRecordsRequest
(*CreateDomainRecordsResponse)(nil), // 10: api.CreateDomainRecordsResponse
(*DNSRecord)(nil), // 11: api.DNSRecord
(*NetworkConfig)(nil), // 12: api.NetworkConfig
(*IP)(nil), // 13: api.IP
(*MachineInfo)(nil), // 14: api.MachineInfo
(*emptypb.Empty)(nil), // 15: google.protobuf.Empty
}
var file_api_pb_cluster_proto_depIdxs = []int32{
12, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
13, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
14, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
14, // 3: api.MachineMember.machine:type_name -> api.MachineInfo
0, // 4: api.MachineMember.state:type_name -> api.MachineMember.MembershipState
4, // 5: api.ListMachinesResponse.machines:type_name -> api.MachineMember
11, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
11, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord
1, // 8: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType
2, // 9: api.Cluster.AddMachine:input_type -> api.AddMachineRequest
15, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
6, // 11: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest
8, // 12: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest
15, // 13: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
15, // 14: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
9, // 15: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
3, // 16: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
5, // 17: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
15, // 18: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
7, // 19: api.Cluster.ReserveDomain:output_type -> api.Domain
7, // 20: api.Cluster.GetDomain:output_type -> api.Domain
7, // 21: api.Cluster.ReleaseDomain:output_type -> api.Domain
10, // 22: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
16, // [16:23] is the sub-list for method output_type
9, // [9:16] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
}
func init() { file_api_pb_cluster_proto_init() }
func file_api_pb_cluster_proto_init() {
if File_api_pb_cluster_proto != nil {
return
}
file_api_pb_common_proto_init()
file_api_pb_machine_proto_init()
if !protoimpl.UnsafeEnabled {
file_api_pb_cluster_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*AddMachineRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*AddMachineResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*MachineMember); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*ListMachinesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*RemoveMachineRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*Domain); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*ReserveDomainRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*CreateDomainRecordsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[8].Exporter = func(v any, i int) any {
switch v := v.(*CreateDomainRecordsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any {
switch v := v.(*DNSRecord); 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_cluster_proto_rawDesc,
NumEnums: 2,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_api_pb_cluster_proto_goTypes,
DependencyIndexes: file_api_pb_cluster_proto_depIdxs,
EnumInfos: file_api_pb_cluster_proto_enumTypes,
MessageInfos: file_api_pb_cluster_proto_msgTypes,
}.Build()
File_api_pb_cluster_proto = out.File
file_api_pb_cluster_proto_rawDesc = nil
file_api_pb_cluster_proto_goTypes = nil
file_api_pb_cluster_proto_depIdxs = nil
}
@@ -2,16 +2,15 @@ syntax = "proto3";
package api;
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
option go_package = "github.com/psviderski/uncloud/api/pb";
import "google/protobuf/empty.proto";
import "internal/machine/api/pb/common.proto";
import "internal/machine/api/pb/machine.proto";
import "api/pb/common.proto";
import "api/pb/machine.proto";
service Cluster {
rpc AddMachine(AddMachineRequest) returns (AddMachineResponse);
rpc ListMachines(google.protobuf.Empty) returns (ListMachinesResponse);
rpc UpdateMachine(UpdateMachineRequest) returns (UpdateMachineResponse);
rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty);
rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
@@ -51,20 +50,6 @@ message ListMachinesResponse {
repeated MachineMember machines = 1;
}
message UpdateMachineRequest {
// Machine to update
string machine_id = 1;
// Updated machine information
optional string name = 2;
optional IP public_ip = 3;
repeated IPPort endpoints = 4;
}
message UpdateMachineResponse {
MachineInfo machine = 1;
}
message RemoveMachineRequest {
string id = 1;
}
@@ -2,7 +2,7 @@
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: internal/machine/api/pb/cluster.proto
// source: api/pb/cluster.proto
package pb
@@ -22,7 +22,6 @@ const _ = grpc.SupportPackageIsVersion9
const (
Cluster_AddMachine_FullMethodName = "/api.Cluster/AddMachine"
Cluster_ListMachines_FullMethodName = "/api.Cluster/ListMachines"
Cluster_UpdateMachine_FullMethodName = "/api.Cluster/UpdateMachine"
Cluster_RemoveMachine_FullMethodName = "/api.Cluster/RemoveMachine"
Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain"
Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain"
@@ -36,7 +35,6 @@ const (
type ClusterClient interface {
AddMachine(ctx context.Context, in *AddMachineRequest, opts ...grpc.CallOption) (*AddMachineResponse, error)
ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, error)
UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error)
RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error)
GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
@@ -72,16 +70,6 @@ func (c *clusterClient) ListMachines(ctx context.Context, in *emptypb.Empty, opt
return out, nil
}
func (c *clusterClient) UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateMachineResponse)
err := c.cc.Invoke(ctx, Cluster_UpdateMachine_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *clusterClient) RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
@@ -138,7 +126,6 @@ func (c *clusterClient) CreateDomainRecords(ctx context.Context, in *CreateDomai
type ClusterServer interface {
AddMachine(context.Context, *AddMachineRequest) (*AddMachineResponse, error)
ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error)
UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error)
RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error)
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error)
GetDomain(context.Context, *emptypb.Empty) (*Domain, error)
@@ -160,9 +147,6 @@ func (UnimplementedClusterServer) AddMachine(context.Context, *AddMachineRequest
func (UnimplementedClusterServer) ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMachines not implemented")
}
func (UnimplementedClusterServer) UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateMachine not implemented")
}
func (UnimplementedClusterServer) RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveMachine not implemented")
}
@@ -235,24 +219,6 @@ func _Cluster_ListMachines_Handler(srv interface{}, ctx context.Context, dec fun
return interceptor(ctx, in, info, handler)
}
func _Cluster_UpdateMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateMachineRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ClusterServer).UpdateMachine(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Cluster_UpdateMachine_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ClusterServer).UpdateMachine(ctx, req.(*UpdateMachineRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Cluster_RemoveMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveMachineRequest)
if err := dec(in); err != nil {
@@ -358,10 +324,6 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListMachines",
Handler: _Cluster_ListMachines_Handler,
},
{
MethodName: "UpdateMachine",
Handler: _Cluster_UpdateMachine_Handler,
},
{
MethodName: "RemoveMachine",
Handler: _Cluster_RemoveMachine_Handler,
@@ -384,5 +346,5 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/machine/api/pb/cluster.proto",
Metadata: "api/pb/cluster.proto",
}
@@ -2,7 +2,7 @@
// versions:
// protoc-gen-go v1.34.2
// protoc v5.27.3
// source: internal/machine/api/pb/common.proto
// source: api/pb/common.proto
package pb
@@ -58,11 +58,11 @@ func (x LogEntry_StreamType) String() string {
}
func (LogEntry_StreamType) Descriptor() protoreflect.EnumDescriptor {
return file_internal_machine_api_pb_common_proto_enumTypes[0].Descriptor()
return file_api_pb_common_proto_enumTypes[0].Descriptor()
}
func (LogEntry_StreamType) Type() protoreflect.EnumType {
return &file_internal_machine_api_pb_common_proto_enumTypes[0]
return &file_api_pb_common_proto_enumTypes[0]
}
func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
@@ -71,7 +71,7 @@ func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
func (LogEntry_StreamType) EnumDescriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7, 0}
return file_api_pb_common_proto_rawDescGZIP(), []int{7, 0}
}
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
@@ -81,8 +81,12 @@ type Metadata struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// ID of the machine the response came from.
MachineId string `protobuf:"bytes,4,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
// Name of the machine the response came from.
MachineName string `protobuf:"bytes,5,opt,name=machine_name,json=machineName,proto3" json:"machine_name,omitempty"`
// Address of the machine the response came from.
Machine string `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"`
MachineAddr string `protobuf:"bytes,1,opt,name=machine_addr,json=machineAddr,proto3" json:"machine_addr,omitempty"`
// error is set if the request to upstream failed. The rest of the response is undefined.
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
// error as a gRPC Status message.
@@ -92,7 +96,7 @@ type Metadata struct {
func (x *Metadata) Reset() {
*x = Metadata{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[0]
mi := &file_api_pb_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -105,7 +109,7 @@ func (x *Metadata) String() string {
func (*Metadata) ProtoMessage() {}
func (x *Metadata) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[0]
mi := &file_api_pb_common_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -118,12 +122,26 @@ func (x *Metadata) ProtoReflect() protoreflect.Message {
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
func (*Metadata) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{0}
return file_api_pb_common_proto_rawDescGZIP(), []int{0}
}
func (x *Metadata) GetMachine() string {
func (x *Metadata) GetMachineId() string {
if x != nil {
return x.Machine
return x.MachineId
}
return ""
}
func (x *Metadata) GetMachineName() string {
if x != nil {
return x.MachineName
}
return ""
}
func (x *Metadata) GetMachineAddr() string {
if x != nil {
return x.MachineAddr
}
return ""
}
@@ -154,7 +172,7 @@ type Empty struct {
func (x *Empty) Reset() {
*x = Empty{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[1]
mi := &file_api_pb_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -167,7 +185,7 @@ func (x *Empty) String() string {
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[1]
mi := &file_api_pb_common_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -180,7 +198,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message {
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{1}
return file_api_pb_common_proto_rawDescGZIP(), []int{1}
}
func (x *Empty) GetMetadata() *Metadata {
@@ -203,7 +221,7 @@ type EmptyResponse struct {
func (x *EmptyResponse) Reset() {
*x = EmptyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[2]
mi := &file_api_pb_common_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -216,7 +234,7 @@ func (x *EmptyResponse) String() string {
func (*EmptyResponse) ProtoMessage() {}
func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[2]
mi := &file_api_pb_common_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -229,7 +247,7 @@ func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
func (*EmptyResponse) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{2}
return file_api_pb_common_proto_rawDescGZIP(), []int{2}
}
func (x *EmptyResponse) GetMessages() []*Empty {
@@ -250,7 +268,7 @@ type IP struct {
func (x *IP) Reset() {
*x = IP{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[3]
mi := &file_api_pb_common_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -263,7 +281,7 @@ func (x *IP) String() string {
func (*IP) ProtoMessage() {}
func (x *IP) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[3]
mi := &file_api_pb_common_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -276,7 +294,7 @@ func (x *IP) ProtoReflect() protoreflect.Message {
// Deprecated: Use IP.ProtoReflect.Descriptor instead.
func (*IP) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{3}
return file_api_pb_common_proto_rawDescGZIP(), []int{3}
}
func (x *IP) GetIp() []byte {
@@ -298,7 +316,7 @@ type IPPort struct {
func (x *IPPort) Reset() {
*x = IPPort{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[4]
mi := &file_api_pb_common_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -311,7 +329,7 @@ func (x *IPPort) String() string {
func (*IPPort) ProtoMessage() {}
func (x *IPPort) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[4]
mi := &file_api_pb_common_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -324,7 +342,7 @@ func (x *IPPort) ProtoReflect() protoreflect.Message {
// Deprecated: Use IPPort.ProtoReflect.Descriptor instead.
func (*IPPort) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{4}
return file_api_pb_common_proto_rawDescGZIP(), []int{4}
}
func (x *IPPort) GetIp() *IP {
@@ -353,7 +371,7 @@ type IPPrefix struct {
func (x *IPPrefix) Reset() {
*x = IPPrefix{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[5]
mi := &file_api_pb_common_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -366,7 +384,7 @@ func (x *IPPrefix) String() string {
func (*IPPrefix) ProtoMessage() {}
func (x *IPPrefix) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[5]
mi := &file_api_pb_common_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -379,7 +397,7 @@ func (x *IPPrefix) ProtoReflect() protoreflect.Message {
// Deprecated: Use IPPrefix.ProtoReflect.Descriptor instead.
func (*IPPrefix) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{5}
return file_api_pb_common_proto_rawDescGZIP(), []int{5}
}
func (x *IPPrefix) GetIp() *IP {
@@ -412,7 +430,7 @@ type LogsRequest struct {
func (x *LogsRequest) Reset() {
*x = LogsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
mi := &file_api_pb_common_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -425,7 +443,7 @@ func (x *LogsRequest) String() string {
func (*LogsRequest) ProtoMessage() {}
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
mi := &file_api_pb_common_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -438,7 +456,7 @@ func (x *LogsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.
func (*LogsRequest) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{6}
return file_api_pb_common_proto_rawDescGZIP(), []int{6}
}
func (x *LogsRequest) GetId() string {
@@ -490,7 +508,7 @@ type LogEntry struct {
func (x *LogEntry) Reset() {
*x = LogEntry{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
mi := &file_api_pb_common_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -503,7 +521,7 @@ func (x *LogEntry) String() string {
func (*LogEntry) ProtoMessage() {}
func (x *LogEntry) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
mi := &file_api_pb_common_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -516,7 +534,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
func (*LogEntry) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7}
return file_api_pb_common_proto_rawDescGZIP(), []int{7}
}
func (x *LogEntry) GetStream() LogEntry_StreamType {
@@ -540,79 +558,82 @@ func (x *LogEntry) GetMessage() []byte {
return nil
}
var File_internal_machine_api_pb_common_proto protoreflect.FileDescriptor
var File_api_pb_common_proto protoreflect.FileDescriptor
var file_internal_machine_api_pb_common_proto_rawDesc = []byte{
0x0a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x66, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a,
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x22, 0x37, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70,
0x22, 0x35, 0x0a, 0x06, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52,
0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65,
0x66, 0x69, 0x78, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04,
0x62, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73,
0x22, 0x75, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73,
0x69, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63,
0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06,
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10,
0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a,
0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x37, 0x5a, 0x35,
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, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
var file_api_pb_common_proto_rawDesc = []byte{
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0xb1, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x0d,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a,
0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22, 0x35, 0x0a, 0x06, 0x49,
0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12,
0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x17,
0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, 0x75, 0x0a, 0x0b, 0x4c,
0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f,
0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x6c,
0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05,
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e, 0x74,
0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54,
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06,
0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52,
0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 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_internal_machine_api_pb_common_proto_rawDescOnce sync.Once
file_internal_machine_api_pb_common_proto_rawDescData = file_internal_machine_api_pb_common_proto_rawDesc
file_api_pb_common_proto_rawDescOnce sync.Once
file_api_pb_common_proto_rawDescData = file_api_pb_common_proto_rawDesc
)
func file_internal_machine_api_pb_common_proto_rawDescGZIP() []byte {
file_internal_machine_api_pb_common_proto_rawDescOnce.Do(func() {
file_internal_machine_api_pb_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_machine_api_pb_common_proto_rawDescData)
func file_api_pb_common_proto_rawDescGZIP() []byte {
file_api_pb_common_proto_rawDescOnce.Do(func() {
file_api_pb_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_common_proto_rawDescData)
})
return file_internal_machine_api_pb_common_proto_rawDescData
return file_api_pb_common_proto_rawDescData
}
var file_internal_machine_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_internal_machine_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_internal_machine_api_pb_common_proto_goTypes = []any{
var file_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_api_pb_common_proto_goTypes = []any{
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
(*Metadata)(nil), // 1: api.Metadata
(*Empty)(nil), // 2: api.Empty
@@ -625,7 +646,7 @@ var file_internal_machine_api_pb_common_proto_goTypes = []any{
(*status.Status)(nil), // 9: google.rpc.Status
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
}
var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
var file_api_pb_common_proto_depIdxs = []int32{
9, // 0: api.Metadata.status:type_name -> google.rpc.Status
1, // 1: api.Empty.metadata:type_name -> api.Metadata
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
@@ -640,13 +661,13 @@ var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
0, // [0:7] is the sub-list for field type_name
}
func init() { file_internal_machine_api_pb_common_proto_init() }
func file_internal_machine_api_pb_common_proto_init() {
if File_internal_machine_api_pb_common_proto != nil {
func init() { file_api_pb_common_proto_init() }
func file_api_pb_common_proto_init() {
if File_api_pb_common_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_internal_machine_api_pb_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Metadata); i {
case 0:
return &v.state
@@ -658,7 +679,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[1].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*Empty); i {
case 0:
return &v.state
@@ -670,7 +691,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[2].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*EmptyResponse); i {
case 0:
return &v.state
@@ -682,7 +703,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[3].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*IP); i {
case 0:
return &v.state
@@ -694,7 +715,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[4].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*IPPort); i {
case 0:
return &v.state
@@ -706,7 +727,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[5].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*IPPrefix); i {
case 0:
return &v.state
@@ -718,7 +739,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*LogsRequest); i {
case 0:
return &v.state
@@ -730,7 +751,7 @@ func file_internal_machine_api_pb_common_proto_init() {
return nil
}
}
file_internal_machine_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
file_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*LogEntry); i {
case 0:
return &v.state
@@ -747,19 +768,19 @@ func file_internal_machine_api_pb_common_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_machine_api_pb_common_proto_rawDesc,
RawDescriptor: file_api_pb_common_proto_rawDesc,
NumEnums: 1,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_internal_machine_api_pb_common_proto_goTypes,
DependencyIndexes: file_internal_machine_api_pb_common_proto_depIdxs,
EnumInfos: file_internal_machine_api_pb_common_proto_enumTypes,
MessageInfos: file_internal_machine_api_pb_common_proto_msgTypes,
GoTypes: file_api_pb_common_proto_goTypes,
DependencyIndexes: file_api_pb_common_proto_depIdxs,
EnumInfos: file_api_pb_common_proto_enumTypes,
MessageInfos: file_api_pb_common_proto_msgTypes,
}.Build()
File_internal_machine_api_pb_common_proto = out.File
file_internal_machine_api_pb_common_proto_rawDesc = nil
file_internal_machine_api_pb_common_proto_goTypes = nil
file_internal_machine_api_pb_common_proto_depIdxs = nil
File_api_pb_common_proto = out.File
file_api_pb_common_proto_rawDesc = nil
file_api_pb_common_proto_goTypes = nil
file_api_pb_common_proto_depIdxs = nil
}
@@ -2,17 +2,22 @@ syntax = "proto3";
package api;
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
option go_package = "github.com/psviderski/uncloud/api/pb";
// Vendored at internal/machine/api/vendor/google/rpc/status.proto.
// Vendored at api/pb/vendor/google/rpc/status.proto.
import "google/rpc/status.proto";
import "google/protobuf/timestamp.proto";
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
// about the machine that responded to the request.
message Metadata {
// ID of the machine the response came from.
string machine_id = 4;
// Name of the machine the response came from.
string machine_name = 5;
// Address of the machine the response came from.
string machine = 1;
string machine_addr = 1;
// error is set if the request to upstream failed. The rest of the response is undefined.
string error = 2;
// error as a gRPC Status message.
File diff suppressed because it is too large Load Diff
@@ -2,10 +2,10 @@ syntax = "proto3";
package api;
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
option go_package = "github.com/psviderski/uncloud/api/pb";
import "google/protobuf/empty.proto";
import "internal/machine/api/pb/common.proto";
import "api/pb/common.proto";
service Docker {
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
@@ -2,7 +2,7 @@
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: internal/machine/api/pb/docker.proto
// source: api/pb/docker.proto
package pb
@@ -807,5 +807,5 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
ServerStreams: true,
},
},
Metadata: "internal/machine/api/pb/docker.proto",
Metadata: "api/pb/docker.proto",
}
+1986
View File
File diff suppressed because it is too large Load Diff
+206
View File
@@ -0,0 +1,206 @@
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.
// 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;
}
@@ -2,7 +2,7 @@
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: internal/machine/api/pb/machine.proto
// source: api/pb/machine.proto
package pb
@@ -26,6 +26,8 @@ const (
Machine_Token_FullMethodName = "/api.Machine/Token"
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
Machine_InspectMachine_FullMethodName = "/api.Machine/InspectMachine"
Machine_WaitForStoreVersion_FullMethodName = "/api.Machine/WaitForStoreVersion"
Machine_UpdateMachine_FullMethodName = "/api.Machine/UpdateMachine"
Machine_InspectWireGuardNetwork_FullMethodName = "/api.Machine/InspectWireGuardNetwork"
Machine_Reset_FullMethodName = "/api.Machine/Reset"
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
@@ -45,6 +47,24 @@ type MachineClient interface {
Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error)
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
InspectMachine(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectMachineResponse, error)
// 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.
// 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.
WaitForStoreVersion(ctx context.Context, in *WaitForStoreVersionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// UpdateMachine updates the configuration of the machine.
UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error)
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
InspectWireGuardNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectWireGuardNetworkResponse, error)
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
@@ -121,6 +141,26 @@ func (c *machineClient) InspectMachine(ctx context.Context, in *emptypb.Empty, o
return out, nil
}
func (c *machineClient) WaitForStoreVersion(ctx context.Context, in *WaitForStoreVersionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Machine_WaitForStoreVersion_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *machineClient) UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateMachineResponse)
err := c.cc.Invoke(ctx, Machine_UpdateMachine_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *machineClient) InspectWireGuardNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectWireGuardNetworkResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InspectWireGuardNetworkResponse)
@@ -183,6 +223,24 @@ type MachineServer interface {
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error)
// 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.
// 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.
WaitForStoreVersion(context.Context, *WaitForStoreVersionRequest) (*emptypb.Empty, error)
// UpdateMachine updates the configuration of the machine.
UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error)
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
InspectWireGuardNetwork(context.Context, *emptypb.Empty) (*InspectWireGuardNetworkResponse, error)
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
@@ -217,6 +275,12 @@ func (UnimplementedMachineServer) Inspect(context.Context, *emptypb.Empty) (*Mac
func (UnimplementedMachineServer) InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InspectMachine not implemented")
}
func (UnimplementedMachineServer) WaitForStoreVersion(context.Context, *WaitForStoreVersionRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method WaitForStoreVersion not implemented")
}
func (UnimplementedMachineServer) UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateMachine not implemented")
}
func (UnimplementedMachineServer) InspectWireGuardNetwork(context.Context, *emptypb.Empty) (*InspectWireGuardNetworkResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InspectWireGuardNetwork not implemented")
}
@@ -358,6 +422,42 @@ func _Machine_InspectMachine_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _Machine_WaitForStoreVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(WaitForStoreVersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MachineServer).WaitForStoreVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Machine_WaitForStoreVersion_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MachineServer).WaitForStoreVersion(ctx, req.(*WaitForStoreVersionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Machine_UpdateMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateMachineRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MachineServer).UpdateMachine(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Machine_UpdateMachine_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MachineServer).UpdateMachine(ctx, req.(*UpdateMachineRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Machine_InspectWireGuardNetwork_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 {
@@ -454,6 +554,14 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
MethodName: "InspectMachine",
Handler: _Machine_InspectMachine_Handler,
},
{
MethodName: "WaitForStoreVersion",
Handler: _Machine_WaitForStoreVersion_Handler,
},
{
MethodName: "UpdateMachine",
Handler: _Machine_UpdateMachine_Handler,
},
{
MethodName: "InspectWireGuardNetwork",
Handler: _Machine_InspectWireGuardNetwork_Handler,
@@ -474,5 +582,5 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
ServerStreams: true,
},
},
Metadata: "internal/machine/api/pb/machine.proto",
Metadata: "api/pb/machine.proto",
}
+165
View File
@@ -0,0 +1,165 @@
package caddystorage
import (
"context"
"errors"
"fmt"
"log/slog"
"slices"
"time"
"github.com/psviderski/uncloud/pkg/client"
"github.com/psviderski/uncloud/pkg/distlock"
)
const (
lockPrefix = "caddy_storage:"
storeReplicationTimeout = 10 * time.Second
)
// Lock acquires an automatically renewed distributed lock and waits for the local store to catch up with versions
// observed on responding machines. Writers using the same lock can then read locally. Unavailable machines may have
// writes that this wait does not cover, and reads outside a lock remain eventually consistent.
func (s *Storage) Lock(ctx context.Context, name string) (err error) {
if name == "" {
return errors.New("lock name is empty")
}
// Count the call first. If Cleanup has already observed zero, Caddy has cancelled s.ctx and the check below rejects
// this call before it uses the client.
s.lockOps.Add(1)
// A failed Lock owns its lifecycle through any lease rollback. A successful Lock transfers that responsibility to
// Unlock, which keeps the client open until its release attempt finishes.
defer func() {
if err != nil {
s.lockOps.Add(-1)
}
}()
if s.ctx.Err() != nil {
return errors.New("storage is closed")
}
// Stop acquisition retries when Caddy unloads the module (cancels s.ctx), even if the caller's context
// is still active.
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
stopOnCleanup := context.AfterFunc(s.ctx, func() {
cancel(context.Cause(s.ctx))
})
defer stopOnCleanup()
log := s.log.With("lock", name)
started := time.Now()
log.Debug("acquiring lock", "ttl", time.Duration(s.LockTTL))
lease, err := s.locker.Acquire(ctx, lockPrefix+name)
if err != nil {
log.Debug("failed to acquire lock", "duration", time.Since(started), "error", err)
return fmt.Errorf("acquire lock '%s': %w", name, err)
}
log.Debug("lock lease acquired", "duration", time.Since(started))
// Release the lease if the lock acquisition fails after this point.
// Unlock will take care of releasing the lease on success.
defer func() {
if err == nil {
return
}
if releaseErr := s.releaseLock(ctx, name, lease, "cancelled acquisition"); releaseErr != nil {
err = errors.Join(err, releaseErr)
}
}()
// Catch up with the latest store versions observed on responding machines to increase the chance of reading
// the latest writes on them locally.
version, machines, err := s.clusterStoreVersion(ctx, log)
if err != nil {
return err
}
waitStarted := time.Now()
log.Debug("waiting for local store replication", "machine_names", machines, "store_version", version)
waitCtx, cancelWait := context.WithTimeout(ctx, storeReplicationTimeout)
err = s.client.WaitForStoreVersion(waitCtx, version)
cancelWait()
if err != nil {
return fmt.Errorf("wait for local store replication: %w", err)
}
log.Debug("local store replication complete", "duration", time.Since(waitStarted))
s.locksMu.Lock()
defer s.locksMu.Unlock()
if s.ctx.Err() != nil {
return errors.New("storage is closed")
}
if lost := context.Cause(lease.Context()); lost != nil {
return lost
}
if _, exists := s.locks[name]; exists {
return errors.New("lock is already tracked by this storage instance")
}
s.locks[name] = lease
log.Debug("lock acquired", "duration", time.Since(started))
return nil
}
// clusterStoreVersion returns the per-actor maximum store versions from responding machines and their names.
func (s *Storage) clusterStoreVersion(ctx context.Context, log *slog.Logger) (map[string]uint64, []string, error) {
ctx, cancel := context.WithTimeout(ctx, distlock.DefaultMaxNodeCallTimeout)
defer cancel()
resp, err := s.client.MachineClient.InspectMachine(client.ProxyMachinesContext(ctx, nil), nil)
if err != nil {
return nil, nil, fmt.Errorf("inspect machines for store versions: %w", err)
}
maxVersion := make(map[string]uint64)
machines := make([]string, 0, len(resp.Machines))
for _, m := range resp.Machines {
if m.Metadata.Error != "" {
log.Debug("skipping machine when collecting store versions",
"id", m.Metadata.MachineId, "name", m.Metadata.MachineName, "error", m.Metadata.Error)
continue
}
machines = append(machines, m.Metadata.MachineName)
for actor, v := range m.StoreVersion {
maxVersion[actor] = max(maxVersion[actor], v)
}
}
slices.Sort(machines)
return maxVersion, machines, nil
}
// Unlock releases a previously acquired distributed lock.
func (s *Storage) Unlock(ctx context.Context, name string) error {
s.locksMu.Lock()
lease, exists := s.locks[name]
if exists {
delete(s.locks, name)
}
s.locksMu.Unlock()
if !exists {
return fmt.Errorf("lock '%s' is not held by this storage instance", name)
}
defer s.lockOps.Add(-1)
// Release stops renewal even on error. Any nodes that cannot be reached will let the lease expire.
if err := s.releaseLock(ctx, name, lease, "unlock"); err != nil {
return fmt.Errorf("release lock '%s': %w", name, err)
}
return nil
}
// releaseLock releases the lease with a context that ignores cancellation and logs the release attempt.
func (s *Storage) releaseLock(ctx context.Context, name string, lease *distlock.Lease, reason string) error {
// Unlock and rollback must attempt node cleanup even if the caller or module has already been cancelled.
ctx = context.WithoutCancel(ctx)
log := s.log.With("lock", name, "reason", reason)
started := time.Now()
log.Debug("releasing lock")
if err := lease.Release(ctx); err != nil {
log.Debug("failed to release lock", "duration", time.Since(started), "error", err)
return err
}
log.Debug("lock released", "duration", time.Since(started))
return nil
}
+211
View File
@@ -0,0 +1,211 @@
// Package caddystorage provides Caddy storage backed by an Uncloud cluster.
package caddystorage
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"sync/atomic"
"time"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/certmagic"
"github.com/psviderski/uncloud/pkg/client"
"github.com/psviderski/uncloud/pkg/client/connector"
"github.com/psviderski/uncloud/pkg/distlock"
)
const (
// ModuleID is the Caddy module ID for Uncloud storage.
ModuleID = "caddy.storage.uncloud"
// DefaultSocketPath is the default path to the Uncloud API socket.
DefaultSocketPath = "/run/uncloud/uncloud.sock"
// DefaultLockTTL is the default duration of a distributed lock lease.
DefaultLockTTL = 20 * time.Second
// lockCleanupTimeout bounds how long an unloaded module waits for active lock operations when cleaning up.
lockCleanupTimeout = 5 * time.Minute
)
//nolint:gochecknoinits // Caddy modules must register during package initialisation.
func init() {
caddy.RegisterModule(new(Storage))
}
// Storage implements a Caddy storage backend that uses an Uncloud cluster to store assets such as TLS certificates.
type Storage struct {
// Socket is the path to the Uncloud API socket.
// Defaults to /run/uncloud/uncloud.sock when not set.
Socket string `json:"socket,omitempty"`
// LockTTL is the duration of a distributed lock after which it expires if not renewed. Locks renew automatically
// until unlocked. If an instance crashes or cannot renew, expiry allows another instance to acquire the stale lock.
// Longer durations tolerate longer interruptions but delay recovery after a crash. Normal unlocks release the lock
// immediately.
// Defaults to 20 seconds when not set.
LockTTL caddy.Duration `json:"lock_ttl,omitempty"`
client *client.Client
locker *distlock.Locker
log *slog.Logger
// ctx is the module context from Provision. Caddy cancels it when it unloads the module and calls Cleanup.
ctx context.Context
// locksMu protects locks.
locksMu sync.Mutex
// locks maps successfully acquired lock names to held leases.
locks map[string]*distlock.Lease
// lockOps counts Lock calls until they fail or their locks are released.
lockOps atomic.Int64
}
// CaddyModule returns the Caddy module information.
func (*Storage) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: ModuleID,
New: func() caddy.Module { return new(Storage) },
}
}
// Provision connects the storage to the local Uncloud API and initialises the distributed locker.
func (s *Storage) Provision(ctx caddy.Context) error {
s.ctx = ctx
s.log = ctx.Slogger()
if s.Socket == "" {
s.Socket = DefaultSocketPath
}
if s.LockTTL == 0 {
s.LockTTL = caddy.Duration(DefaultLockTTL)
}
if s.LockTTL < 0 {
return errors.New("lock_ttl must be positive")
}
cli, err := client.New(ctx, connector.NewUnixConnector(s.Socket))
if err != nil {
return fmt.Errorf("connect to Uncloud API: %w", err)
}
locker, err := cli.NewLocker(distlock.Config{
LeaseDuration: time.Duration(s.LockTTL),
})
if err != nil {
_ = cli.Close()
return fmt.Errorf("create distributed locker: %w", err)
}
s.client = cli
s.locker = locker
s.locks = make(map[string]*distlock.Lease)
s.log.Debug("module provisioned", "socket", s.Socket, "lock_ttl", time.Duration(s.LockTTL))
return nil
}
// Cleanup keeps the Uncloud API connection open so acquired lock leases can renew until Caddy releases them. It closes
// the connection after all lock operations finish or the cleanup timeout expires.
func (s *Storage) Cleanup() error {
if s.client == nil {
// Provision failed before opening the storage.
return nil
}
started := time.Now()
s.log.Debug("cleaning up module", "locks", s.lockOps.Load())
go func() {
timer := time.NewTimer(lockCleanupTimeout)
defer timer.Stop()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
timedOut := false
for !timedOut && s.lockOps.Load() > 0 {
select {
case <-ticker.C:
case <-timer.C:
timedOut = true
}
}
if remaining := s.lockOps.Load(); timedOut && remaining > 0 {
s.log.Debug("timed out waiting for active locks to be unlocked",
"locks", remaining, "timeout", lockCleanupTimeout)
}
if err := s.client.Close(); err != nil {
s.log.Debug("failed to clean up module", "duration", time.Since(started), "error", err)
return
}
s.log.Debug("module cleanup complete", "duration", time.Since(started))
}()
return nil
}
// CertMagicStorage returns the provisioned CertMagic storage implementation.
func (s *Storage) CertMagicStorage() (certmagic.Storage, error) {
return s, nil
}
// UnmarshalCaddyfile configures Uncloud storage from the Caddyfile global storage block.
//
// {
// storage uncloud {
// socket /run/uncloud/uncloud.sock
// lock_ttl 20s
// }
// }
func (s *Storage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // Skip the module name 'uncloud'.
// Reject inline arguments. NextArg leaves an opening brace for NextBlock.
if d.NextArg() {
return d.ArgErr()
}
// Read the optional options block, skipping its surrounding braces.
for d.NextBlock(0) {
switch d.Val() {
case "socket":
// Require a socket path on the same line as 'socket' option.
if !d.NextArg() {
return d.ArgErr()
}
s.Socket = d.Val()
// Reject extra arguments after the socket path.
if d.NextArg() {
return d.ArgErr()
}
case "lock_ttl":
if !d.NextArg() {
return d.ArgErr()
}
ttl, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid lock_ttl '%s': %v", d.Val(), err)
}
if ttl <= 0 {
return d.Err("lock_ttl must be positive")
}
if d.NextArg() {
return d.ArgErr()
}
s.LockTTL = caddy.Duration(ttl)
default:
return d.Errf("unknown uncloud storage option: '%s'", d.Val())
}
}
return nil
}
var (
_ caddy.Module = (*Storage)(nil)
_ caddy.Provisioner = (*Storage)(nil)
_ caddy.CleanerUpper = (*Storage)(nil)
_ caddy.StorageConverter = (*Storage)(nil)
_ caddyfile.Unmarshaler = (*Storage)(nil)
_ certmagic.Storage = (*Storage)(nil)
)
+83
View File
@@ -0,0 +1,83 @@
package caddystorage
import (
"context"
"fmt"
"io/fs"
"github.com/caddyserver/certmagic"
"github.com/psviderski/uncloud/api/pb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Store writes a value to the cluster store.
func (s *Storage) Store(ctx context.Context, key string, value []byte) error {
if _, err := s.client.CaddyStorage.Store(ctx, &pb.StoreCaddyStorageRequest{Key: key, Value: value}); err != nil {
return storageError("store", key, err)
}
return nil
}
// Load reads a value from the cluster store.
func (s *Storage) Load(ctx context.Context, key string) ([]byte, error) {
resp, err := s.client.CaddyStorage.Load(ctx, &pb.LoadCaddyStorageRequest{Key: key})
if err != nil {
return nil, storageError("load", key, err)
}
return resp.Value, nil
}
// Delete removes a key and its descendants from the cluster store.
func (s *Storage) Delete(ctx context.Context, key string) error {
if _, err := s.client.CaddyStorage.Delete(ctx, &pb.DeleteCaddyStorageRequest{Key: key}); err != nil {
return storageError("delete", key, err)
}
return nil
}
// Exists reports whether a key exists in the cluster store.
func (s *Storage) Exists(ctx context.Context, key string) bool {
_, err := s.Stat(ctx, key)
return err == nil
}
// List returns keys under prefix from the cluster store.
func (s *Storage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) {
resp, err := s.client.CaddyStorage.List(ctx, &pb.ListCaddyStorageRequest{
Prefix: prefix,
Recursive: recursive,
})
if err != nil {
return nil, storageError("list", prefix, err)
}
return resp.Keys, nil
}
// Stat returns information about a key in the cluster store.
func (s *Storage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error) {
resp, err := s.client.CaddyStorage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: key})
if err != nil {
return certmagic.KeyInfo{}, storageError("stat", key, err)
}
info := certmagic.KeyInfo{
Key: resp.Key,
Size: resp.Size,
IsTerminal: resp.IsTerminal,
}
if resp.UpdatedAt != nil {
if err := resp.UpdatedAt.CheckValid(); err != nil {
return certmagic.KeyInfo{}, storageError("stat", key, fmt.Errorf("invalid updated_at timestamp: %w", err))
}
info.Modified = resp.UpdatedAt.AsTime()
}
return info, nil
}
func storageError(operation, key string, err error) error {
if status.Code(err) == codes.NotFound {
err = fs.ErrNotExist
}
return fmt.Errorf("uncloud storage: %s key %q: %w", operation, key, err)
}
+4
View File
@@ -0,0 +1,4 @@
_ _ ___
| | | |/ __|
| |_| | (__
\__,_|\___|
@@ -48,10 +48,7 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
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
}
ctx = clusterClient.ProxySingleMachineContext(ctx, opts.machine)
}
config, err := clusterClient.Caddy.GetConfig(ctx, nil)
@@ -12,10 +12,10 @@ import (
"charm.land/lipgloss/v2"
"github.com/docker/cli/cli/streams"
"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/completion"
"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/client"
"github.com/spf13/cobra"
+29
View File
@@ -0,0 +1,29 @@
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
}
@@ -12,6 +12,7 @@ func NewRootCommand() *cobra.Command {
cmd.AddCommand(
NewConfigCommand(),
NewDeployCommand(),
NewLogsCommand(),
)
return cmd
}
+103
View File
@@ -0,0 +1,103 @@
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)
})
}
}
@@ -14,6 +14,7 @@ func NewConnectionCommand() *cobra.Command {
Use: "connection",
Aliases: []string{"conn"},
Short: "Choose a new default connection for the current context.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return selectConnection(uncli)
@@ -5,6 +5,7 @@ import (
"maps"
"slices"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/spf13/cobra"
@@ -15,6 +16,7 @@ func NewListCommand() *cobra.Command {
Use: "ls",
Aliases: []string{"list"},
Short: "List available cluster contexts.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return list(uncli)
@@ -49,6 +51,6 @@ func list(uncli *cli.CLI) error {
t.Row(name, current, fmt.Sprintf("%d", connCount))
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -10,6 +10,7 @@ func NewRootCommand() *cobra.Command {
Use: "ctx",
Aliases: []string{"context"},
Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return selectContext(uncli)
@@ -11,6 +11,7 @@ func NewShowCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Show current cluster context.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return show(uncli)
@@ -8,6 +8,7 @@ import (
"charm.land/huh/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/completion"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/spf13/cobra"
)
@@ -17,6 +18,7 @@ func NewUseCommand() *cobra.Command {
Short: "Switch to a different cluster context.",
Long: "Switch to a different cluster context. If no context is provided, " +
"a list of available contexts will be displayed for selection.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
@@ -49,6 +51,10 @@ func selectContext(uncli *cli.CLI) error {
if len(uncli.Config.Contexts) == 0 {
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))
+9 -1
View File
@@ -163,6 +163,14 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
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{
ForceRecreate: opts.recreate,
SkipHealthMonitor: opts.skipHealth,
@@ -203,7 +211,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.
if !opts.yes {
if !tui.IsStdinTerminal() {
if !tui.IsTerminalAvailable() {
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")
}
@@ -5,9 +5,9 @@ import (
"errors"
"fmt"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
"github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/cmd/uc/caddy"
"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/client"
"github.com/spf13/cobra"
+8 -18
View File
@@ -88,19 +88,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
}
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)
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
@@ -115,12 +102,15 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
var rows []imageRow
for _, machineImages := range clusterImages {
// Get machine name for better readability.
machineName := machineImages.Metadata.Machine
if m := allMachines.FindByNameOrID(machineName); m != nil {
machineName = m.Machine.Name
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.
machineName := machineImages.Metadata.MachineName
store := "docker"
if machineImages.ContainerdStore {
store = "containerd"
@@ -189,7 +179,7 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
})
// Print the images in a table format.
fmt.Println(formatImageTable(rows))
lipgloss.Println(formatImageTable(rows))
return nil
}
+1 -1
View File
@@ -3,7 +3,7 @@ package main
import (
"strings"
"github.com/psviderski/uncloud/cmd/uncloud/image"
"github.com/psviderski/uncloud/cmd/uc/image"
"github.com/spf13/cobra"
)
@@ -8,10 +8,9 @@ import (
"strings"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/compose/v2/pkg/progress"
"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/config"
"github.com/psviderski/uncloud/internal/cli/tui"
@@ -29,6 +28,8 @@ type addOptions struct {
sshKey string
version string
wgEndpoints []string
wgPort int
wgMTU int
yes bool
}
@@ -39,13 +40,19 @@ func NewAddCommand() *cobra.Command {
Short: "Add a remote machine to a 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:
[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`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
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 {
uncli := cmd.Context().Value("cli").(*cli.CLI)
// Determine connection mode and strip scheme.
@@ -70,14 +77,15 @@ Connection methods:
return add(cmd.Context(), uncli, remoteMachine, opts)
},
}
cmd.Flags().StringVarP(&opts.name, "name", "n", "", "Assign a name to the machine.")
cmd.Flags().StringVarP(&opts.name, "name", "n", "",
"Assign a name to the machine. (default is the machine's hostname)")
cmd.Flags().BoolVar(
&opts.noCaddy, "no-caddy", false,
"Don't deploy Caddy reverse proxy service to the machine.",
)
cmd.Flags().BoolVar(
&opts.noInstall, "no-install", false,
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
"Skip installation of Docker and the Uncloud daemon on the machine. "+
"Assumes they're already installed and running.",
)
cmd.Flags().StringVar(
@@ -92,17 +100,25 @@ Connection methods:
)
cmd.Flags().StringVar(
&opts.version, "version", "latest",
"Version of the Uncloud daemon to install on the machine.",
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
)
cmd.Flags().StringSliceVar(
&opts.wgEndpoints, "wg-endpoint", nil,
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
"WireGuard endpoint address that other machines in the cluster should use to establish "+
"WireGuard connections\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", network.WireGuardPort)+
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\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.",
)
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,
"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]")
@@ -125,17 +141,26 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
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{
MachineName: opts.name,
PublicIP: publicIP,
RemoteMachine: remoteMachine,
SkipInstall: opts.noInstall,
Version: opts.version,
WireguardMTU: opts.wgMTU,
WireguardPort: opts.wgPort,
AutoConfirm: opts.yes,
}
if len(opts.wgEndpoints) > 0 {
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
if err != nil {
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
}
@@ -154,19 +179,9 @@ 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.
err = spinner.New().
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()
err = tui.RunSpinner(ctx, "Waiting for the machine to join the cluster...", func(ctx context.Context) error {
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
})
if err != nil {
return fmt.Errorf("wait for machine to join the cluster: %w", err)
}
+72
View File
@@ -0,0 +1,72 @@
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)
}
@@ -7,14 +7,13 @@ import (
"strings"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
"github.com/psviderski/uncloud/cmd/uncloud/dns"
"github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/cmd/uc/caddy"
"github.com/psviderski/uncloud/cmd/uc/dns"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/machine/cluster"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/pkg/api"
@@ -33,6 +32,8 @@ type initOptions struct {
sshKey string
version string
wgEndpoints []string
wgPort int
wgMTU int
yes bool
}
@@ -44,6 +45,9 @@ func NewInitCommand() *cobra.Command {
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.
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:
[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`,
@@ -61,9 +65,12 @@ Connection methods:
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
// TODO: support initialising a cluster on the local machine.
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
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 {
uncli := cmd.Context().Value("cli").(*cli.CLI)
var remoteMachine *cli.RemoteMachine
@@ -100,7 +107,7 @@ Connection methods:
"API endpoint for the Uncloud DNS service.")
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().StringVar(
&opts.network, "network", cluster.DefaultNetwork.String(),
@@ -116,7 +123,7 @@ Connection methods:
)
cmd.Flags().BoolVar(
&opts.noInstall, "no-install", false,
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
"Skip installation of Docker and the Uncloud daemon on the machine. "+
"Assumes they're already installed and running.",
)
cmd.Flags().StringVar(
@@ -131,17 +138,25 @@ Connection methods:
)
cmd.Flags().StringVar(
&opts.version, "version", "latest",
"Version of the Uncloud daemon to install on the machine.",
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
)
cmd.Flags().StringSliceVar(
&opts.wgEndpoints, "wg-endpoint", nil,
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
"WireGuard endpoint address that other machines in the cluster should use to establish "+
"WireGuard connections\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", network.WireGuardPort)+
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\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.",
)
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,
"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]")
@@ -178,6 +193,13 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
}
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{
Context: opts.context,
MachineName: opts.name,
@@ -186,11 +208,13 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
RemoteMachine: remoteMachine,
SkipInstall: opts.noInstall,
Version: opts.version,
WireguardMTU: opts.wgMTU,
WireguardPort: opts.wgPort,
AutoConfirm: opts.yes,
}
if len(opts.wgEndpoints) > 0 {
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
if err != nil {
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
}
@@ -206,19 +230,9 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
// 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
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
err = spinner.New().
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()
err = tui.RunSpinner(ctx, "Waiting for the cluster to be ready...", func(ctx context.Context) error {
return client.WaitClusterReady(ctx, 1*time.Minute)
})
if err != nil {
return fmt.Errorf("wait for cluster to be ready: %w", err)
}
@@ -3,10 +3,12 @@ package machine
import (
"context"
"fmt"
"slices"
"strings"
"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/journal"
"github.com/psviderski/uncloud/pkg/api"
"github.com/psviderski/uncloud/pkg/client"
"github.com/spf13/cobra"
@@ -18,14 +20,14 @@ func NewLogsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "logs [SERVICE...]",
Aliases: []string{"log"},
Short: "View systemd service logs.",
Long: `View logs from the specified systemd service(s) across all machines in the cluster.
Short: "View system service logs.",
Long: `View logs from the specified system service(s) across all machines in the cluster.
Use -m to restrict to specific machines.
Supported services:
uncloud the Uncloud daemon
docker the Docker daemon
uncloud-corrosion the Corrosion distributed state store
corrosion the Corrosion distributed state store
docker the Docker daemon
uncloud the Uncloud daemon
If no services are specified, streams logs from the uncloud service.`,
Example: ` # View recent logs for the uncloud service.
@@ -36,7 +38,7 @@ If no services are specified, streams logs from the uncloud service.`,
uc machine logs -f uncloud
# View logs from multiple services.
uc machine logs uncloud docker uncloud-corrosion
uc machine logs uncloud docker corrosion
# Show last 20 lines per machine (default is 100).
uc machine logs -n 20 docker
@@ -48,7 +50,7 @@ If no services are specified, streams logs from the uncloud service.`,
uc machine logs --since 3h --until 1h30m docker
# View logs only from specific machines.
uc machine logs -m machine1,machine2 uncloud uncloud-corrosion`,
uc machine logs -m machine1,machine2 uncloud corrosion`,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return runLogs(cmd.Context(), uncli, args, options)
@@ -56,17 +58,19 @@ If no services are specified, streams logs from the uncloud service.`,
}
cmd.Flags().AddFlagSet(logs.Flags(&options))
completion.MachinesFlag(cmd)
return cmd
}
func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Options) error {
if len(units) == 0 {
units = []string{journal.UnitUncloud}
func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.Options) error {
if len(services) == 0 {
services = []string{api.SystemServiceUncloud}
}
for _, unit := range units {
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, ", "))
}
}
@@ -101,27 +105,27 @@ func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Opti
machineNames = append(machineNames, m.Machine.Name)
}
// Collect one log stream per unit. MachineLogs merges across machines internally.
unitStreams := make([]<-chan api.ServiceLogEntry, 0, len(units))
for _, unit := range units {
ch, err := c.MachineLogs(ctx, unit, logsOpts)
// Collect one log stream per service. MachineLogs merges across machines internally.
serviceStreams := make([]<-chan api.ServiceLogEntry, 0, len(services))
for _, service := range services {
ch, err := c.MachineLogs(ctx, service, logsOpts)
if err != nil {
return fmt.Errorf("stream logs for systemd service '%s': %w", unit, err)
return fmt.Errorf("stream logs for system service '%s': %w", service, err)
}
unitStreams = append(unitStreams, ch)
serviceStreams = append(serviceStreams, ch)
}
var stream <-chan api.ServiceLogEntry
if len(unitStreams) == 1 {
stream = unitStreams[0]
if len(serviceStreams) == 1 {
stream = serviceStreams[0]
} else {
// Each MachineLogs stream already runs its own inner merger with stall detection,
// so the outer merger across units skips it to avoid duplicate warnings.
merger := client.NewLogMerger(unitStreams, client.LogMergerOptions{})
// so the outer merger across services skips it to avoid duplicate warnings.
merger := client.NewLogMerger(serviceStreams, client.LogMergerOptions{})
stream = merger.Stream()
}
formatter := logs.NewFormatter(machineNames, units, opts.UTC)
formatter := logs.NewFormatter(machineNames, services, opts.UTC)
// Print merged logs.
for entry := range stream {
@@ -2,10 +2,12 @@ package machine
import (
"context"
"encoding/json"
"fmt"
"net/netip"
"strings"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/machine/network"
@@ -13,19 +15,29 @@ import (
)
func NewListCommand() *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List machines in a cluster.",
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return list(cmd.Context(), uncli)
return list(cmd.Context(), uncli, output)
},
}
cmd.Flags().StringVarP(&output, "output", "o", "",
"Output format: 'json' or empty for a human-readable table.")
return cmd
}
func list(ctx context.Context, uncli *cli.CLI) error {
func list(ctx context.Context, uncli *cli.CLI, output string) error {
if output != "" && output != "json" {
return fmt.Errorf("unsupported output format '%s' (supported: json)", output)
}
client, err := uncli.ConnectCluster(ctx)
if err != nil {
return fmt.Errorf("connect to cluster: %w", err)
@@ -37,9 +49,19 @@ func list(ctx context.Context, uncli *cli.CLI) error {
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.
t := tui.NewTable()
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS", "MACHINE ID")
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS",
"OS", "KERNEL", "ARCH", "DOCKER", "VERSION")
for _, member := range machines {
m := member.Machine
@@ -58,17 +80,46 @@ func list(ctx context.Context, uncli *cli.CLI) error {
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(
m.Name,
capitalise(member.State.String()),
subnet.String(),
publicIP,
strings.Join(endpoints, tui.Faint.Render(", ")),
member.Machine.Id,
osName,
kernel,
arch,
dockerVersion,
daemonVersion,
)
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -13,10 +13,10 @@ import (
"charm.land/lipgloss/v2/tree"
"github.com/docker/compose/v2/pkg/progress"
"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/completion"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api"
"github.com/spf13/cobra"
)
@@ -63,15 +63,16 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
}
defer client.Close()
// Verify the machine exists and list all service containers on it including stopped ones.
mctx, machines, err := client.ProxyMachinesContext(ctx, []string{nameOrID})
// Verify the machine exists in the cluster.
member, err := client.InspectMachine(ctx, nameOrID)
if err != nil {
return err
return fmt.Errorf("inspect machine '%s': %w", nameOrID, err)
}
if len(machines) == 0 {
return fmt.Errorf("machine '%s' not found in the cluster", nameOrID)
}
m := machines[0].Machine
m := member.Machine
// Create a proxy context for the machine being removed.
// This is used for calls that need to run directly on that machine.
rmCtx := client.ProxySingleMachineContext(ctx, m.Id)
// Verify if the machine being removed is the proxy machine we're connected to.
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
@@ -103,7 +104,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
if reset {
// Check if the machine is up and has service containers.
listOpts := container.ListOptions{All: true}
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
machineContainers, err := client.Docker.ListServiceContainers(rmCtx, "", listOpts)
if err == nil {
reachable = true
containers = machineContainers[0].Containers
@@ -113,7 +114,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
plural = "s"
}
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
fmt.Println(formatContainerTree(containers))
lipgloss.Println(formatContainerTree(containers))
fmt.Println()
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
"and reset it to the uninitialised state.")
@@ -135,8 +136,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
return fmt.Errorf("confirm removal: %w", err)
}
if !confirmed {
fmt.Println("Cancelled. Machine was not removed.")
return nil
return cli.Cancelled("Cancelled. Machine was not removed.")
}
}
@@ -150,20 +150,22 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
fmt.Println()
}
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
return fmt.Errorf("remove machine from cluster: %w", err)
}
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
// 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(mctx, &pb.ResetRequest{})
_, err = client.MachineClient.Reset(rmCtx, &pb.ResetRequest{})
if err != nil {
fmt.Printf("WARNING: Failed to reset machine: %v\n", err)
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 {
return fmt.Errorf("remove machine from cluster: %w", err)
}
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
// Remove the connection to the machine from the uncloud config if it exists.
if uncli.Config != nil {
contextName := uncli.ContextOverrideOrCurrent()
@@ -6,6 +6,7 @@ import (
"sort"
"time"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/spf13/cobra"
@@ -38,10 +39,7 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
defer client.Close()
// Setup context to proxy request to all machines.
ctx, _, err = client.ProxyMachinesContext(ctx, nil)
if err != nil {
return fmt.Errorf("setup proxy context: %w", err)
}
ctx = client.ProxyMachinesContext(ctx, nil)
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
if err != nil {
@@ -51,6 +49,15 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
// Map machine IDs to names for display from the response.
machineNames := make(map[string]string)
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 {
continue
}
@@ -101,7 +108,7 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
t.Row(r.machine, r.peer, tui.FormatRTT(r.median), formatRTTStdDev(r.stdDev))
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -6,9 +6,9 @@ import (
"net/netip"
"strings"
"github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/completion"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/spf13/cobra"
)
@@ -70,7 +70,8 @@ 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 "+
"WireGuard connections\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", network.WireGuardPort)+
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n",
network.DefaultWireGuardPort)+
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.",
)
@@ -89,16 +90,13 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
}
defer client.Close()
// First, resolve the machine to get its ID
// Resolve the machine to capture its current configuration for the before/after report and to validate existence.
machine, err := client.InspectMachine(ctx, machineNameOrID)
if err != nil {
return fmt.Errorf("find machine: %w", err)
}
// Build the update request
req := &pb.UpdateMachineRequest{
MachineId: machine.Machine.Id,
}
req := &pb.UpdateMachineRequest{}
if opts.name != "" {
req.Name = &opts.name
@@ -121,7 +119,7 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
// Parse and set endpoints if the flag was explicitly provided.
if cmd.Flags().Changed("wg-endpoint") {
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
endpoints, err := cli.ParseWireGuardEndpoints(expanded, network.DefaultWireGuardPort)
if err != nil {
return err
}
@@ -131,8 +129,7 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
req.Endpoints = endpoints
}
// Perform the update operation
updatedMachine, err := client.UpdateMachine(ctx, req)
updatedMachine, err := client.UpdateMachine(ctx, machine.Machine.Id, req)
if err != nil {
return fmt.Errorf("update machine: %w", err)
}
+45 -16
View File
@@ -8,22 +8,23 @@ import (
"os"
"strings"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context"
"github.com/psviderski/uncloud/cmd/uncloud/dns"
"github.com/psviderski/uncloud/cmd/uncloud/image"
cmdmachine "github.com/psviderski/uncloud/cmd/uncloud/machine"
"github.com/psviderski/uncloud/cmd/uncloud/service"
"github.com/psviderski/uncloud/cmd/uncloud/volume"
"github.com/psviderski/uncloud/cmd/uncloud/wg"
"github.com/psviderski/uncloud/cmd/uc/caddy"
cmdcontext "github.com/psviderski/uncloud/cmd/uc/context"
"github.com/psviderski/uncloud/cmd/uc/dns"
"github.com/psviderski/uncloud/cmd/uc/image"
cmdmachine "github.com/psviderski/uncloud/cmd/uc/machine"
"github.com/psviderski/uncloud/cmd/uc/service"
"github.com/psviderski/uncloud/cmd/uc/volume"
"github.com/psviderski/uncloud/cmd/uc/wg"
"github.com/psviderski/uncloud/internal/cli"
"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/log"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/version"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type globalOptions struct {
@@ -39,10 +40,16 @@ func main() {
cmd := &cobra.Command{
Use: "uc",
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
Version: version.String(),
SilenceUsage: true,
SilenceErrors: true,
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, "context", "UNCLOUD_CONTEXT")
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
@@ -113,15 +120,11 @@ func main() {
defaultHelpFunc(c, args)
// Only show links for the root 'uc' command.
if c.Name() == "uc" {
urlStyle := lipgloss.NewStyle().
Underline(true).
Foreground(lipgloss.Color("12")) // light blue
fmt.Fprintln(c.OutOrStdout())
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
urlStyle.Render("https://uncloud.run/docs"))
tui.URLStyle.Render(version.DocsURL))
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
urlStyle.Render("https://uncloud.run/discord"))
tui.URLStyle.Render(version.DiscordURL))
}
})
@@ -136,6 +139,7 @@ func main() {
NewDocsCommand(),
NewImagesCommand(),
NewPsCommand(),
NewProxyCommand(),
caddy.NewRootCommand(),
cmdcontext.NewRootCommand(),
dns.NewRootCommand(),
@@ -151,6 +155,7 @@ func main() {
service.NewScaleCommand("service"),
service.NewStartCommand("service"),
service.NewStopCommand("service"),
NewVersionCommand(),
volume.NewRootCommand(),
wg.NewRootCommand(),
)
@@ -162,3 +167,27 @@ func main() {
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())
})
}
+116
View File
@@ -0,0 +1,116 @@
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
View File
@@ -0,0 +1,175 @@
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 ""
}
}
+14 -48
View File
@@ -6,7 +6,6 @@ import (
"sort"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-units"
@@ -83,20 +82,10 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
defer clusterClient.Close()
var containers []containerInfo
err = spinner.New().
Title(" Collecting container info...").
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()
err = tui.RunSpinner(ctx, "Collecting container info...", func(ctx context.Context) error {
containers, err = collectContainers(ctx, clusterClient)
return err
})
if err != nil {
return fmt.Errorf("collect containers: %w", err)
}
@@ -193,23 +182,12 @@ func printContainers(containers []containerInfo) error {
}
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
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
}
}
listCtx := client.ProxyMachinesContext(ctx, nil)
// List all service containers across all machines in the cluster.
machineContainers, err := cli.Docker.ListServiceContainers(
@@ -221,29 +199,17 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
var containers []containerInfo
for _, msc := range machineContainers {
// Metadata can be nil if the request was broadcasted to only one machine.
if msc.Metadata == nil && len(machineContainers) > 1 {
return nil, fmt.Errorf("something went wrong with gRPC proxy: metadata is missing for a machine response")
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
if msc.Metadata == nil {
tui.PrintWarning("metadata is missing in response from unknown server")
continue
}
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
}
}
machineName := msc.Metadata.MachineName
if msc.Metadata != nil && msc.Metadata.Error != "" {
tui.PrintWarning(fmt.Sprintf("failed to list containers on machine %s: %s", machineName,
msc.Metadata.Error))
if msc.Metadata.Error != "" {
tui.PrintWarning(fmt.Sprintf("failed to list service containers on machine %s: %s",
machineName, msc.Metadata.Error))
continue
}
+105
View File
@@ -0,0 +1,105 @@
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)
}
}
}
@@ -6,6 +6,7 @@ import (
"slices"
"time"
"charm.land/lipgloss/v2"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/psviderski/uncloud/internal/cli"
@@ -54,15 +55,6 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
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("Name: %s\n", svc.Name)
fmt.Printf("Mode: %s\n", svc.Mode)
@@ -97,7 +89,7 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
for _, ctr := range allContainers {
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
machine := machinesNamesByID[ctr.MachineID]
machine := ctr.MachineName
if machine == "" {
machine = ctr.MachineID
}
@@ -135,6 +127,6 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
}
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -59,7 +59,7 @@ If no services are specified, streams logs from all services defined in the Comp
uc logs -m machine1,machine2 web api`,
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return runLogs(cmd.Context(), uncli, args, options)
return RunLogs(cmd.Context(), uncli, args, options)
},
GroupID: groupID,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
@@ -74,12 +74,10 @@ If no services are specified, streams logs from all services defined in the Comp
cmd.Flags().AddFlagSet(logs.Flags(&options))
completion.MachinesFlag(cmd)
completion.MachinesFlag(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 {
serviceArgs, err := logs.ParseServiceArgs(args)
if err != nil {
return err
@@ -6,6 +6,7 @@ import (
"slices"
"strings"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/pkg/api"
@@ -82,6 +83,6 @@ func list(ctx context.Context, uncli *cli.CLI) error {
t.Row(row...)
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -93,15 +93,17 @@ func NewRunCommand(groupID string) *cobra.Command {
"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,
"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_port:container_port[/protocol]@host\n"+
"Format: [hostname:]container_port[/protocol] or [host_ip|host_prefix:]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"+
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
"Examples:\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 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"+
// TODO: add support for publishing L4 tcp/udp ports.
//" -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")
" -p 53:5353/udp@host Bind UDP port 5353 to host port 53\n"+
" -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,
fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').",
api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever))
@@ -30,9 +30,11 @@ func NewScaleCommand(groupID string) *cobra.Command {
Short: "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),
RunE: func(cmd *cobra.Command, args []string) error {
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 {
uncli := cmd.Context().Value("cli").(*cli.CLI)
opts.service = args[0]
@@ -136,7 +138,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
// Ask for confirmation unless auto-confirmed with --yes.
if !opts.yes {
if !tui.IsStdinTerminal() {
if !tui.IsTerminalAvailable() {
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")
}
+82
View File
@@ -0,0 +1,82 @@
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
}
@@ -8,9 +8,9 @@ import (
"charm.land/huh/v2"
"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/completion"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api"
"github.com/spf13/cobra"
)
@@ -6,6 +6,7 @@ import (
"slices"
"strings"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/completion"
"github.com/psviderski/uncloud/internal/cli/tui"
@@ -95,6 +96,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
t.Row(v.Volume.Name, v.Volume.Driver, v.MachineName)
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
@@ -92,8 +92,7 @@ func remove(ctx context.Context, uncli *cli.CLI, names []string, opts removeOpti
return fmt.Errorf("confirm removal: %w", err)
}
if !confirmed {
fmt.Println("Cancelled. No volumes were removed.")
return nil
return cli.Cancelled("Cancelled. No volumes were removed.")
}
}
+4 -6
View File
@@ -6,11 +6,12 @@ import (
"strings"
"time"
"charm.land/lipgloss/v2"
"github.com/docker/go-units"
"github.com/psviderski/uncloud/api/pb"
"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/machine/api/pb"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
@@ -60,10 +61,7 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
if opts.machine != "" {
// Proxy requests to the specified machine.
ctx, _, err = client.ProxyMachinesContext(ctx, []string{opts.machine})
if err != nil {
return err
}
ctx = client.ProxySingleMachineContext(ctx, opts.machine)
}
resp, err := client.MachineClient.InspectWireGuardNetwork(ctx, nil)
@@ -136,6 +134,6 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
)
}
fmt.Println(t)
lipgloss.Println(t)
return nil
}
+4 -3
View File
@@ -10,9 +10,10 @@ import (
func NewRemoveCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "rm [NAME]",
Short: "Remove a cluster.",
Args: cobra.MaximumNArgs(1),
Use: "rm [NAME]",
Short: "Remove a cluster.",
Aliases: []string{"remove", "delete"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
-441
View File
@@ -1,441 +0,0 @@
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)
}
}
+5
View File
@@ -0,0 +1,5 @@
_ _ _
_ _ _ __ ___| | ___ _ _ __| | __| |
| | | | '_ \ / __| |/ _ \| | | |/ _` |/ _` |
| |_| | | | | (__| | (_) | |_| | (_| | (_| |
\__,_|_| |_|\___|_|\___/ \__,_|\__,_|\__,_|
+7 -3
View File
@@ -7,9 +7,11 @@ import (
"os/signal"
"syscall"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/daemon"
"github.com/psviderski/uncloud/internal/log"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/metrics"
"github.com/psviderski/uncloud/internal/version"
"github.com/spf13/cobra"
)
@@ -24,7 +26,7 @@ func main() {
cmd := &cobra.Command{
Use: "uncloudd",
Short: "Uncloud machine daemon.",
Version: version.String(),
Long: "Uncloud machine daemon.\n" + tui.URLStyle.Render(version.WebsiteURL),
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -39,11 +41,13 @@ func main() {
},
}
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")
// Add dial-stdio subcommand.
cmd.AddCommand(newDialStdioCommand())
cmd.AddCommand(newVersionCommand())
metrics.Version.WithLabelValues(version.String()).Set(1)
// ctx is canceled when the daemon command is interrupted.
ctx, cancel := context.WithCancel(context.Background())
+82
View File
@@ -0,0 +1,82 @@
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
}
+10 -5
View File
@@ -19,16 +19,21 @@ require (
github.com/siderolabs/discovery-api v0.1.4
github.com/siderolabs/discovery-client v0.1.9
go.uber.org/zap v1.27.0
golang.org/x/net v0.43.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.74.2
)
require (
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/sprig/v3 v3.2.3 // indirect
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.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/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -123,17 +128,17 @@ require (
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gotest.tools/v3 v3.5.2 // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)
+14 -4
View File
@@ -5,6 +5,8 @@ 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/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
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.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
@@ -38,6 +40,8 @@ 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.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
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/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -660,8 +664,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-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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -694,8 +698,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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -707,6 +711,8 @@ 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -725,6 +731,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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-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/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
@@ -776,6 +784,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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/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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
+1 -1
View File
@@ -81,7 +81,7 @@ func TestDiscovery() error {
}
endpoints[i] = &pb.Endpoint{
Ip: ip,
Port: network.WireGuardPort,
Port: network.DefaultWireGuardPort,
}
}
if err = client.SetLocalData(
@@ -1,4 +1,4 @@
package connector
package wg
import (
"context"
@@ -7,39 +7,48 @@ import (
"net/netip"
"strconv"
"github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/grpcversion"
"github.com/psviderski/uncloud/internal/machine/constants"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/machine/network/tunnel"
"github.com/psviderski/uncloud/pkg/client"
"github.com/psviderski/uncloud/internal/secret"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// Machine describes a remote machine that the experimental connector can reach.
type Machine struct {
Host string
PublicKey secret.Secret
}
type User interface {
ManagementIP() netip.Addr
PrivateKey() secret.Secret
}
// WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel
// to one of the cluster machines.
type WireGuardConnector struct {
user *client.User
machines []config.MachineConnection
tun *tunnel.Tunnel
user User
machines []Machine
tun *Tunnel
}
func NewWireGuardConnector(user *client.User, machines []config.MachineConnection) *WireGuardConnector {
func NewWireGuardConnector(user User, machines []Machine) *WireGuardConnector {
return &WireGuardConnector{
user: user,
machines: machines,
}
}
// TODO: handle context cancelation.
// TODO: handle context cancellation.
func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
if len(c.machines) == 0 {
return nil, fmt.Errorf("no machines to connect to")
}
// TODO: iterate over machines and try to connect to each one until successful.
// For now, try to connect to only the first machine.
// For now, try to connect to only the first machine.
machine := c.machines[0]
endpointIPs, err := net.LookupIP(machine.Host)
if err != nil {
@@ -49,25 +58,24 @@ func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, err
if err != nil {
return nil, fmt.Errorf("parse IP address %q: %w", endpointIPs[0].String(), err)
}
endpoint := netip.AddrPortFrom(endpointAddr, tunnel.DefaultEndpointPort)
endpoint := netip.AddrPortFrom(endpointAddr, DefaultEndpointPort)
machineManagementIP := network.ManagementIP(machine.PublicKey)
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(constants.MachineAPIPort))
tunCfg := &tunnel.Config{
tunCfg := &Config{
LocalAddress: c.user.ManagementIP(),
LocalPrivateKey: c.user.PrivateKey(),
RemotePublicKey: machine.PublicKey,
RemoteNetwork: netip.PrefixFrom(machineManagementIP, 128),
Endpoint: endpoint,
}
if c.tun, err = tunnel.Connect(tunCfg); err != nil {
if c.tun, err = Connect(tunCfg); err != nil {
return nil, fmt.Errorf("establish WireGuard tunnel to %q: %w", endpoint, err)
}
conn, err := grpc.NewClient(
machineAPIAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(defaultServiceConfig),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return c.tun.DialContext(ctx, "tcp", addr)
}),
@@ -1,4 +1,4 @@
package tunnel
package wg
import (
"context"
@@ -15,7 +15,7 @@ import (
const (
DefaultEndpointPort = 51820
// DefaultKeepaliveInterval is sensible interval that works with a wide variety of firewalls.
// DefaultKeepaliveInterval is a sensible interval that works with a wide variety of firewalls.
DefaultKeepaliveInterval = 25 * time.Second
)
+142 -136
View File
@@ -1,20 +1,22 @@
module github.com/psviderski/uncloud
go 1.26
go 1.26.0
require (
charm.land/bubbles/v2 v2.0.0
charm.land/bubbletea/v2 v2.0.2
charm.land/huh/v2 v2.0.1
charm.land/lipgloss/v2 v2.0.1
github.com/BurntSushi/toml v1.5.0
github.com/BurntSushi/toml v1.6.0
github.com/Masterminds/semver v1.5.0
github.com/Masterminds/squirrel v1.5.4
github.com/alecthomas/chroma/v2 v2.20.0
github.com/caddyserver/caddy/v2 v2.8.4
github.com/alecthomas/chroma/v2 v2.24.1
github.com/caarlos0/go-version v0.2.2
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/charmbracelet/colorprofile v0.4.2
github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/colorprofile v0.4.3
github.com/charmbracelet/x/ansi v0.11.7
github.com/compose-spec/compose-go/v2 v2.9.0
github.com/containerd/errdefs v1.0.0
github.com/containerd/platforms v1.0.0-rc.1
@@ -29,72 +31,81 @@ require (
github.com/goccy/go-yaml v1.17.1
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.2
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/miekg/dns v1.1.65
github.com/mattn/go-shellwords v1.0.12
github.com/miekg/dns v1.1.73
github.com/mitchellh/mapstructure v1.5.0
github.com/moby/term v0.5.2
github.com/opencontainers/go-digest v1.0.0
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/siderolabs/grpc-proxy v0.5.1
github.com/spf13/cobra v1.10.1
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.1
github.com/vishvananda/netlink v1.3.1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.42.0
golang.org/x/term v0.34.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.org/x/crypto v0.57.0
golang.org/x/net v0.59.0
golang.org/x/sync v0.23.0
golang.org/x/sys v0.48.0
golang.org/x/term v0.46.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a
google.golang.org/grpc v1.74.2
google.golang.org/protobuf v1.36.9
modernc.org/sqlite v1.36.3
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4
google.golang.org/grpc v1.81.0
google.golang.org/protobuf v1.36.12
modernc.org/sqlite v1.49.1
tags.cncf.io/container-device-interface v1.0.1
)
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
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/bigmod v0.1.0 // indirect
filippo.io/edwards25519 v1.2.0 // 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/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect
github.com/KimMachineGun/automemlimit v0.7.5 // indirect
github.com/Masterminds/goutils v1.1.1 // 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.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // 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/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // 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.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // 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.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // 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/presigned-url v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
github.com/aws/smithy-go v1.25.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/caddyserver/certmagic v0.21.4 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/ccoveille/go-safecast/v2 v2.0.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/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 // 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/term v0.2.2 // indirect
@@ -103,6 +114,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/console v1.0.5 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
@@ -115,15 +127,16 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-oidc/v3 v3.17.0 // 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/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/dgraph-io/ristretto v0.2.0 // 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/distribution/distribution/v3 v3.0.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/docker/buildx v0.29.1 // indirect
github.com/docker/cli-docs-tool v0.10.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
@@ -137,38 +150,34 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsevents v0.2.0 // indirect
github.com/fvbommel/sortorder v1.1.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/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // 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-sql-driver/mysql v1.9.3 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/cel-go v0.28.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -176,42 +185,36 @@ require (
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.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/inconshreveable/mousetrap v1.1.0 // indirect
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // 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/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.2 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/acmez/v2 v2.0.3 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/pkcs11 v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
@@ -237,46 +240,49 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pires/go-proxyproto v0.12.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.48.2 // indirect
github.com/prometheus/client_model v0.6.3 // indirect
github.com/prometheus/common v0.71.0 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.22.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.62.0 // 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/v9 v9.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
github.com/shibumi/go-pathspec v1.3.0 // 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/sirupsen/logrus v1.9.4 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/slackhq/nebula v1.6.1 // indirect
github.com/smallstep/certificates v0.26.1 // indirect
github.com/smallstep/nosql v0.6.1 // indirect
github.com/smallstep/pkcs7 v0.1.1 // indirect
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
github.com/slackhq/nebula v1.10.3 // indirect
github.com/smallstep/certificates v0.30.2 // indirect
github.com/smallstep/cli-utils v0.12.2 // indirect
github.com/smallstep/linkedca v0.25.0 // indirect
github.com/smallstep/nosql v0.8.0 // indirect
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // 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/stretchr/objx v0.5.3 // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
@@ -294,55 +300,53 @@ require (
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect
go.opentelemetry.io/otel/log v0.8.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
go.opentelemetry.io/otel/log v0.19.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.step.sm/crypto v0.81.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.2.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/text v0.28.0 // 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
go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20260213171211-a408498e5541 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.16.0 // indirect
golang.org/x/tools v0.50.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/api v0.277.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // 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/apimachinery v0.32.3 // indirect
@@ -350,10 +354,12 @@ require (
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/libc v1.72.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/memory v1.11.0 // 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/yaml v1.4.0 // indirect
)
replace charm.land/bubbletea/v2 => github.com/unlabs-dev/bubbletea/v2 v2.0.8-uncloud.1
+341 -431
View File
File diff suppressed because it is too large Load Diff
+47 -13
View File
@@ -8,9 +8,11 @@ import (
"slices"
"github.com/docker/cli/cli/streams"
"github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/cluster"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/sshexec"
"github.com/psviderski/uncloud/pkg/api"
"github.com/psviderski/uncloud/pkg/client"
@@ -174,6 +176,8 @@ type InitClusterOptions struct {
Version string
AutoConfirm bool
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.
@@ -234,6 +238,8 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
MachineName: opts.MachineName,
Network: pb.NewIPPrefix(opts.Network),
WireguardEndpoints: opts.WireguardEndpoints,
WireguardMtu: int32(opts.WireguardMTU),
WireguardPort: int32(opts.WireguardPort),
}
if opts.PublicIP != nil {
if opts.PublicIP.IsValid() {
@@ -317,13 +323,15 @@ type AddMachineOptions struct {
Version string
AutoConfirm bool
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.
// 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.
// Both client should be closed after use by the caller.
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, *client.Client, error) {
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *client.Client, _ *client.Client, err error) {
contextName := cli.ContextOverrideOrCurrent()
c, err := cli.ConnectCluster(ctx)
if err != nil {
@@ -344,16 +352,19 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
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.
// TODO: refactor to use client.InspectMachine.
minfo, err := machineClient.Inspect(ctx, &emptypb.Empty{})
inspectResp, err := machineClient.MachineClient.InspectMachine(ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("inspect machine: %w", err)
}
minfo := inspectResp.Machines[0].Machine
if minfo.Id != "" {
// Check if the machine is already a member of this cluster.
machines, err := c.ListMachines(ctx, nil)
var machines api.MachineMembersList
machines, err = c.ListMachines(ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
}
@@ -401,11 +412,32 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
} else {
endpoints = make([]*pb.IPPort, len(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)
}
}
// 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{
Name: opts.MachineName,
Name: machineName,
Network: &pb.NetworkConfig{
Endpoints: endpoints,
PublicKey: token.PublicKey,
@@ -425,16 +457,16 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
}
// Get the current store DB version from the cluster to pass to the join request.
var storeDBVersion int64
inspectResp, err := c.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
// Snapshot the cluster store version so the new machine can catch up before participating.
var storeVersion map[string]uint64
inspectResp, err = c.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
if err != nil {
// TODO(lhf): remove Unimplemented check when v0.17.0 is released.
if status.Convert(err).Code() != codes.Unimplemented {
return nil, nil, fmt.Errorf("inspect current cluster machine: %w", err)
}
} else {
storeDBVersion = inspectResp.Machines[0].StoreDbVersion
storeVersion = inspectResp.Machines[0].StoreVersion
}
// Get the most up-to-date list of other machines in the cluster to include them in the join request.
@@ -451,9 +483,11 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
// Configure the remote machine to join the cluster.
joinReq := &pb.JoinClusterRequest{
Machine: addResp.Machine,
OtherMachines: otherMachines,
MinStoreDbVersion: storeDBVersion,
Machine: addResp.Machine,
OtherMachines: otherMachines,
MinStoreVersion: storeVersion,
WireguardMtu: int32(opts.WireguardMTU),
WireguardPort: int32(opts.WireguardPort),
}
if _, err = machineClient.JoinCluster(ctx, joinReq); err != nil {
return nil, nil, fmt.Errorf("join cluster: %w", err)

Some files were not shown because too many files have changed in this diff Show More